Мы знаем это T v(x);
называется прямая инициализация, в то время как T v = x;
называется копия инициализация, Это означает, что он будет строить временный T
от x
который будет скопирован / перемещен в v
(который, скорее всего, исключен).
Для инициализации списка стандарт различает две формы в зависимости от контекста. T v{x};
называется прямой список инициализация в то время как T v = {x};
называется копирование списка инициализация:
§8.5.4 [dcl.init.list] p1
[…] Инициализация списка может происходить при прямой инициализации или при инициализации копирования
контексты; инициализация списка в контексте прямой инициализации называется прямой список инициализация и инициализация списка в контексте инициализации копирования называется копирование списка инициализация. […]
Однако в целом стандарте есть только две ссылки. Для прямой инициализации списка это упоминается при создании временных T{x}
(§5.2.3/3
). Для копирования списка инициализации, это для выражения в операторах возврата, таких как return {x};
(§6.6.3/2
).
А как насчет следующего фрагмента?
#include <initializer_list>
struct X{
X(X const&) = delete; // no copy
X(X&&) = delete; // no move
X(std::initializer_list<int>){} // only list-init from 'int's
};
int main(){
X x = {42};
}
Обычно из X x = expr;
шаблон, мы ожидаем, что код не скомпилируется, потому что конструктор перемещения X
определяется как delete
д. Тем не менее, последние версии Clang и GCC прекрасно компилируют приведенный выше код, и после того, как он немного покопался (и обнаружил приведенную выше цитату), это кажется правильным поведением. Стандарт только когда-либо определяет поведение для всей инициализации списка, и вообще не проводит различия между двумя формами, за исключением вышеупомянутых пунктов. Ну, по крайней мере, насколько я вижу, в любом случае.
Итак, чтобы подвести итог моего вопроса еще раз:
Какая польза от разделения инициализации списка на две его формы, если они (по-видимому) делают одно и то же?
Потому что они не сделать то же самое. Как указано в 13.3.1.7 [over.match.list]:
В случае инициализации copy-list, если выбран явный конструктор, инициализация является некорректной.
Короче говоря, вы можете использовать неявное преобразование только в контексте инициализации копирования списка.
Это было явно добавлено, чтобы сделать равномерную инициализацию не единой. Да, я знаю, как это звучит глупо, но терпите меня.
В 2008, N2640 был опубликован (PDF), взглянуть на текущее состояние равномерной инициализации. Посмотрел конкретно на разницу между прямой инициализацией (T{...}
) и копия-инициализация (T = {...}
).
Подводя итог, проблема была в том, что explicit
конструкторы фактически станут бессмысленными. Если у меня есть какой-то тип T
что я хочу иметь возможность быть построенным из целого числа, но я не хочу неявного преобразования, я помечаю конструктор как явный.
Тогда кто-то делает это:
T func()
{
return {1};
}
Без текущей формулировки, это будет называть мой explicit
конструктор. Так что хорошего в том, чтобы сделать конструктор explicit
если это не сильно изменится?
С текущей формулировкой вам нужно как минимум использовать имя напрямую:
T func()
{
return T{1};
}
Других решений пока нет …