Почему синтаксис двойных фигурных скобок не предпочтителен для конструкторов, принимающих 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, если он существует), прежде чем вернуться к ( ... ), Думаю, я бы предпочел, чтобы оба варианта считались одинаково хорошими, и было бы предупреждение или ошибка, если оба варианта возможны.

Другие вопросы по тегам