Инициализация из списка инициализаторов, но без {{{{{{{{...}}}}}}}}?
Недавно я сталкивался с некоторыми проблемами с списками инициализаторов. Рассмотрим программу, в которой хранятся данные, подобные карте.
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
Что вы думаете о первой и последней форме инициализации? Имеет ли смысл иметь в этом случае дополнительные фигурные скобки?
Я предпочитаю первую форму, потому что у нее чистый интерфейс класса. Конструктор, берущий список инициализаторов, загрязняет интерфейс и предлагает мало взамен.
Дополнительные фигурные скобки - это то, к чему мы привыкнем. Так же, как мы привыкли к другим причудам С++.