Почему синтаксис двойных фигурных скобок не предпочтителен для конструкторов, принимающих std::initializer_list
Равномерная инициализация является важной и полезной функцией C++11. Тем не менее, вы не можете просто использовать {}
с тех пор
std::vector<int> a(10, 0); // 10 elements of value zero
std::vector<int> b({10, 0}); // 2 elements of value 10 and 0 respectively
std::vector<int> c{10, 0}; // 2 elements of value 10 and 0 respectively
std::vector<int> d = {10, 0}; // 2 elements of value 10 and 0 respectively
auto e(0); // deduced type is int
auto f = 0; // deduced type is int
auto g{0}; // deduced type is std::initializer_list<int>
auto h = {0}; // deduced type is std::initializer_list<int>
Отмечая, что агрегатная инициализация, например, std::arrays
требует использования {{}}
Мне кажется, что всей проблемы, с которой будет выбран векторный конструктор, можно было бы избежать, потребовав {{}}
вызывать конструкторов, принимающих std::initializer_list
:
std::vector<int> i{10, 0}; // 10 elements of value zero
std::vector<int> j{{10, 0}}; // 2 elements of value 10 and 0 respectively
std::vector<int> k = {10, 0}; // 2 elements of value 10 and 0 respectively
auto l{0}; // deduced type is int
auto m{{0}}; // deduced type is std::initializer_list<int>
auto n = {0}; // deduced type is std::initializer_list<int>
Я уверен, что это обсуждалось, так каковы были причины против этого? Цитата / ссылка из стандартного предложения предпочтительнее в качестве ответа.
Обновить. - В N2532 есть пункт, который гласит:
(3) Вероятные неприятные случаи неоднозначности происходят только для коротких списков инициализаторов [...]
(5) Почему языковые правила должны заставлять программистов, которые хотят, чтобы краткость и контроль неоднозначности (по вполне веским причинам) писали больше, чтобы угодить программистам, которые предпочитают (по совершенно веским причинам) быть более явными - и может быть?
[...]
Предположим, что программист ожидает, что f(X) будет вызван. Как a f(Y) "перехватить" вызов?
(4) Предположим, что X не имеет конструктора списка инициализаторов, а Y имеет. В этом случае приоритет, отданный конструкторам списка инициализаторов, отдает предпочтение угонщику (помните, мы предполагали, что программист каким-то образом ожидал вызова функции f(X)). Это аналогично тому, что кто-то ожидает, что f(y) вызовет f(X), используя пользовательское преобразование, и кто-то придет с f (Y), который точно соответствует. Я думаю, было бы справедливо ожидать, что кто-то, кто использует {…}, вспомнит о возможности конструкторов списков инициализаторов. [акцент мой]
Я предполагаю, что ключ лежит в can, что означает, что вам не нужно использовать одинаковую инициализацию. С помощью {}
правильно трудно, так как:
Вы должны проверять не только конструктор, который хотите вызвать, но и любой конструктор, принимающий
initializer_list
это может победить (и, вероятно, будет) над этим;если вы пишете код, используя
{}
и кто-то в будущем добавляетstd::initializer_list
Конструктор ваш код может сломаться и сделать это молча.
Даже если у вас есть класс A
с конструкторами A(int, bool)
а также A(std::initializer_list<double>)
, последний будет выбран над первым для A a{0, false};
(что IMO - чокнутый), так что мне действительно трудно использовать одинаковую инициализацию для классов, которые имеют или могут иметь (требуются сверхспособности хрустального шара) initializer_list
Конструкторы.
Тот факт, что ваш код может молча взломать меня очень беспокоит.
2 ответа
Вот что сказал Страуструп по этому вопросу:
Единый и универсальный не был разработан, чтобы быть просто четвертой альтернативой. Он был разработан, чтобы быть синтаксисом инициализации, и, к сожалению, [не] выполнимо использовать со всем устаревшим кодом, особенно
vector
, Если бы я разработалvector
сегодня вы бы сказали что-то вродеvector<int> {Count{9}};
чтобы получить счет.
И в ответ на вопрос "Является ли вектор проблемы или {}-init синтаксис?"
Это векторный дизайн: если бы я разработал
vector
сегодня вы бы сказали что-то вродеvector<int> {Count{9}};
чтобы получить счет.Более общая проблема состоит в том, чтобы иметь несколько семантически различных аргументов одного и того же типа, что в конечном итоге приводит к путанице, особенно если они могут появиться на первый взгляд. Например:
vector<int> v(7,2); // 7 (a count) element with the value 2
(Это на самом деле не ответ, а просто обсуждение того, о чем я думал по этому вопросу.)
Я думаю, что я хотел бы, чтобы компиляторы предупреждали в случаях, когда есть двусмысленность, советуя разработчику использовать либо ({ })
(если они действительно хотят initializer_list), или ( )
если они этого не делают. С дополнительным предупреждением, если MostVexingParse является риском! - возможно, рекомендую (( ))
чтобы избежать этого?
(Эта следующая "история", вероятно, не основана на правильной исторической хронологии того, как была разработана функция, но это то, как я понимаю текущие правила в компиляторе.)
В начале у нас были конструкторы:
type t (...);
Тогда у нас была идея позволить {
дать буквальную коллекцию для использования в конструкторах (но также и в других местах).
type t ( {...} );
... вместе с новым initializer_list
введите конструкторы, чтобы соответствовать этому.
Затем нам разрешили заменить ( )
с { }
чтобы избежать самого разборчивого разбора:
type t { ... };
type t { {...} };
Все идет нормально. Чистые расширения языка.
И, наконец, "спорный" дополнение является то, что, когда компилятор видит { ... }
(в качестве конструктора) сначала попытается переписать это как ({ ... })
(вызывая initializer_list, если он существует), прежде чем вернуться к ( ... )
, Думаю, я бы предпочел, чтобы оба варианта считались одинаково хорошими, и было бы предупреждение или ошибка, если оба варианта возможны.