Почему стандарт различает прямую инициализацию списка и инициализацию копирования списка?

Мы знаем это T v(x); называется прямой инициализацией, в то время как T v = x; называется copy-initialization, что означает, что он создаст временный T от x который будет скопирован / перемещен в v (который, скорее всего, исключен).

Для инициализации списка стандарт различает две формы в зависимости от контекста. T v{x}; называется прямой инициализацией списка в то время как T v = {x}; называется copy-list-initialization:

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

Итак, чтобы подвести итог моего вопроса еще раз:

Какая польза от разделения инициализации списка на две его формы, если они (по-видимому) делают одно и то же?

1 ответ

Решение

Потому что они не делают то же самое. Как указано в 13.3.1.7 [over.match.list]:

При инициализации copy-list-init, если выбран явный конструктор, инициализация некорректна.

Короче говоря, вы можете использовать неявное преобразование только в контексте инициализации копирования списка.

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

В 2008 году была опубликована N2640 (PDF), в которой рассматривается текущее состояние равномерной инициализации. Посмотрел конкретно на разницу между прямой инициализацией (T{...}) и копия-инициализация (T = {...}).

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

Тогда кто-то делает это:

T func()
{
  return {1};
}

Без текущей формулировки, это будет называть мой explicit конструктор. Так что хорошего в том, чтобы сделать конструктор explicit если это не сильно изменится?

С текущей формулировкой вам нужно как минимум использовать имя напрямую:

T func()
{
  return T{1};
}
Другие вопросы по тегам