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

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

Итак, чтобы резюмировать мой вопрос еще раз:

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


person Xeo    schedule 19.11.2012    source источник
comment
Если бы вы сделали этот третий конструктор explicit, он перестал бы работать?   -  person Kerrek SB    schedule 20.11.2012
comment
Разница между прямой инициализацией и копией инициализации определена в 8.5 # 16. При прямой инициализации рассматриваются только конструкторы, тогда как при копировании рассматриваются последовательности преобразования, определенные пользователем. Таким образом, явное указание ctor приведет к ошибке компилятора.   -  person chill    schedule 20.11.2012


Ответы (1)


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

При инициализации списка копирования, если выбран явный конструктор, инициализация имеет неправильный формат.

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

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

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

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

Затем кто-то делает это:

T func()
{
  return {1};
}

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

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

T func()
{
  return T{1};
}
person Nicol Bolas    schedule 19.11.2012
comment
Я должен был это знать, так как я постоянно жалуюсь, что конструктор std::tuple и std::unique_ptrs является explicit, поэтому я не могу сделать return {a, b, c};. :) - person Xeo; 20.11.2012
comment
Ну, поскольку единообразная инициализация не работает единообразно для всех типов, я думаю, что фраза «сделать единообразную инициализацию не, гм, единообразной» на самом деле недействительна. Например. в фрагменте X x; X y{x};, y является копией x для всех копируемых типов кроме тех, у которых есть хотя бы один конструктор списка любого типа (для этих типов он обычно не компилируется). Таким образом, в шаблонах вам нужно вернуться к старой инициализации C ++ 03, если вы хотите скопировать или преобразовать. - person jpalecek; 20.11.2012
comment
@jpalecek: Это неправда. Конструктор initializer_list будет использоваться только в том случае, если данный список в фигурных скобках имеет тот же тип, что и конструктор. Следовательно, он будет вызываться только в том случае, если X имеет конструктор X(initializer_list<X>), что маловероятно. И даже если бы это было так, разве это не просто ... скопировало бы члена (ов)? Я не уверен, что вас здесь беспокоит. Обычно речь идет о таких вещах, как vector<int>, где конструктор, принимающий size_t, конфликтует с конструктором initailizer_list. - person Nicol Bolas; 20.11.2012
comment
@NicolBolas: Вы ошибаетесь. Простое присутствие любого initializer_list конструктора означает, что любые другие конструкторы не учитываются, если вы использовали синтаксис в фигурных скобках. Стандарт четко говорит об этом: если T имеет конструктор списка инициализаторов, список аргументов состоит из списка инициализаторов в виде единственного аргумента; в противном случае список аргументов состоит из элементов списка инициализатора. Перечислены применимые конструкторы ... и все. Посмотрите, как это работает: ideone.com/z56OES. Таким образом, вы не можете использовать фигурные скобки для типов, которые случайно могут иметь конструктор списка, если вы имеете в виду копию. - person jpalecek; 20.11.2012
comment
@jpalecek Я считаю, что это ошибка gcc4.5.1, поскольку this компилируется в 4.7.0. b инициализируется с помощью (сгенерированного компилятором) конструктора копирования, а конструктор, принимающий initializer_list, не выбран, хотя он и является предпочтительным, поскольку член (-ы) списка-инициализации в фигурных скобках не может быть преобразован в int. Как говорит Никол в предыдущем комментарии, если бы существовал конструктор A::A(initializer_list<A>), он был бы выбран поверх конструктора копирования. - person Praetorian; 20.11.2012
comment
@jpalecek: Из раздела 13.3.1.7, p1: -Initially, the candidate functions are the initializer-list constructors (8.5.4) of the class T and the argument list consists of the initializer list as a single argument. - If no viable initializer-list constructor is found, overload resolution is performed again, where the candidate functions are all the constructors of the class T and the argument list consists of the elements of the initializer list. Значит, вы, должно быть, смотрите на старую версию стандарта (я нигде не нашел этого утверждения). - person Nicol Bolas; 20.11.2012