Инициализация из списка инициализаторов, но без {{{{{{{{...}}}}}}}}?

Недавно я сталкивался с некоторыми проблемами с списками инициализаторов. Рассмотрим программу, в которой хранятся данные, подобные карте.

struct MyMapLike {
  MyMapLike(std::map<std::string, int> data)
    :data(std::move(data))
  { }

private:
  std::map<std::string, int> data;
};

Это выглядит прямо. Но при инициализации он становится уродливым. Я хочу, чтобы это выглядело как

MyMapLike maps = { { "One", 1 }, { "Two", 2 } };

Но компилятор не хочет принимать это, потому что выше это означает, что он должен искать двухпараметрический конструктор, который может принимать { "One", 1 } и { "Two", 2 } соответственно. Мне нужно добавить дополнительные фигурные скобки, чтобы они выглядели как однопараметрический конструктор, принимающий { { ... }, { ... } }

MyMapLike maps = { { { "One", 1 }, { "Two", 2 } } };

Я бы не хотел так писать. Поскольку у меня есть класс, подобный карте, и инициализатор имеет абстрактное значение списка сопоставлений, я хотел бы использовать прежнюю версию и быть независимым от любых таких данных реализации, таких как уровень вложенности конструкторов.

Одна работа заключается в том, чтобы объявить конструктор списка инициализаторов

struct MyMapLike {
  MyMapLike(std::initializer_list< 
    std::map<std::string, int>::value_type
    > vals)
    :data(vals.begin(), vals.end())
  { }

  MyMapLike(std::map<std::string, int> data)
    :data(std::move(data))
  { }

private:
  std::map<std::string, int> data;
};

Теперь я могу использовать первое, потому что, когда у меня есть конструктор списка инициализаторов, весь список инициализаторов рассматривается как один элемент, а не разделяется на элементы. Но я думаю, что эта отдельная потребность в конструкторе мертва уродлива.

Я ищу руководство:

  • Что вы думаете о первой и последней форме инициализации? Имеет ли смысл иметь в этом случае дополнительные фигурные скобки?
  • Считаете ли вы требование добавления конструктора списка инициализаторов в этом случае плохим?

Если вы согласны со мной в том, что прежний способ инициализации лучше, какие решения вы можете придумать?

Ответ 1

Так как у меня есть подобный карте класс, и инициализатор имеет абстрактное значение списка-списка, я бы хотел использовать предыдущую версию

И проблема заключается в том, что вам нужно предоставить конструкторы, которые позволят вашему классу рассматриваться как карта. Вы назвали ваше решение обходным, но нечего обойти.:)

Но я думаю, что эта отдельная потребность в конструкторе мертва уродливая.

Это, но, к сожалению, поскольку это ваш класс, вы должны указать, как работают списки инициализаторов.

Ответ 2

Что вы думаете о первой и последней форме инициализации? Имеет ли смысл иметь в этом случае дополнительные фигурные скобки?

Я так думаю. Я думаю, что это позволило бы слишком много двусмысленности, чтобы конструктор можно было назвать не только тогда, когда синтаксис соответствует этому конструктору, но когда синтаксис соответствует некоторому конструктору одного аргумента конструктору и т.д. Рекурсивно.

struct A { int i; };
struct B { B(A) {}  B(int) {} };
struct C { C(B) {} };

C c{1};

Считаете ли вы требование добавления конструктора списка инициализаторов в этом случае плохим?

Нет. Он позволяет получить синтаксис, который вы хотите, но не создавая проблем, возникающих, если мы усложняем поиск компилятора для использования конструктором.

Ответ 3

Разве что-то подобное не даст желаемого эффекта (MyMapLike может быть сконструирован любым способом, который std::map может, но не подразумевает преобразование в std::map)?

struct MyMapLike : private std::map<std::string, int>
{
    using map::map;
};

Если это абсолютно положительно должно быть членом, возможно, используйте идеальную перестройку конструктора (я не уверен в точном синтаксисе) по строкам:

struct MyMapLike
{
    template<typename... Initializers>
    MyMapLike(Initializers... init, decltype(new std::map<std::string, int>(...init)) = 0)
      : data(...init)
    { }

private:
    std::map<std::string, int> data;
};

Ответ 4

Вы можете выполнить инициализацию следующим образом:

MyMapLike maps ( { { "One", 1 }, { "Two", 2 } } );

Внешние ( ... ) теперь явно просто окружают конструкторы args, и легче видеть, что внешний { ... } определяет "список".

Он все еще немного подробный, но он избегает ситуации, когда { используется для выполнения двух разных действий, что влияет на читаемость.

Ответ 5

Я согласен, что это уродливо. Обычно я списываю списки инициализаторов, когда я выхожу за пределы очень простых случаев, так как они довольно ограничены. Для вашего случая я бы пошел с boost:: assign, который, хотя и не такой короткий, дает лучшую гибкость и контроль.

Ответ 6

Что вы думаете о первой и последней форме инициализации? Имеет ли смысл иметь в этом случае дополнительные фигурные скобки?

Я предпочитаю первую форму, потому что у нее чистый интерфейс класса. Конструктор, берущий список инициализаторов, загрязняет интерфейс и предлагает мало взамен.

Дополнительные фигурные скобки - это то, к чему мы привыкнем. Так же, как мы привыкли к другим причудам С++.