Концептуально ли инициализация списка копирования вызывает copy ctor?
До C++11 мы можем выполнить инициализацию копирования, написав что-то вроде A a = 1;
что более или менее эквивалентно A a = A(1);
, То есть сначала создается временный объект, а затем вызывается копия ctor. Независимо от исключения копирования, это должно быть так концептуально, и копия ctor должна быть доступна.
При инициализации списка в C++11 мы можем выполнить инициализацию списка копирования, написав A a = {1, 2};
, На мой взгляд, это должно быть более или менее эквивалентно A a = A(1, 2);
, Тем не менее, на GCC и лязг, A a = {1, 2}
компилируется, даже когда ctor для копирования и перемещения недоступен (объявляется как private). Еще, A a = 1;
не компилируется в GCC или clang, если соответствующий ctor копирования / перемещения недоступен. Так, A a = {1, 2};
кажется более или менее эквивалентным A a{1, 2};
которая является прямой инициализацией списка. Разница между этим и реальной прямой инициализацией списка заключается в том, что A a = {1, 2};
не компилируется, если ctor, который принимает два целых числа, является явным. В этом аспекте A a = {1, 2};
напоминает инициализацию копирования.
Итак, мой вопрос: какова точная семантика выражений типа A a = {1, 2};
концептуально? Концептуально, копия elision не стоит на пути.
1 ответ
Стандарт описывает это довольно хорошо; [Dcl.init.list]/3:
Инициализация списка объекта или ссылки типа
T
определяется следующим образом:
- [...]
- В противном случае, если
T
является типом класса, конструкторы рассматриваются. Применимые конструкторы перечисляются, и лучший выбирается через разрешение перегрузки (13.3, 13.3.1.7). Если для преобразования какого-либо из аргументов требуется сужающее преобразование (см. Ниже), программа является некорректной.
[over.match.list] (выделено мной):
Когда объекты неагрегированного класса
T
инициализируются списком (8.5.4), разрешение перегрузки выбирает конструктор в два этапа:
Первоначально функции-кандидаты являются конструкторами списка инициализаторов (8.5.4) класса
T
и список аргументов состоит из списка инициализаторов как единственного аргумента.Если жизнеспособный конструктор списка инициализаторов не найден, разрешение перегрузки выполняется снова, где все функции-кандидаты являются конструкторами класса.
T
и список аргументов состоит из элементов списка инициализатора.Если список инициализаторов не имеет элементов, а T имеет конструктор по умолчанию, первая фаза опускается.
В инициализации копирования списка, еслиexplicit
конструктор выбран, инициализация некорректна.
Следовательно, если конструктор списка инициализаторов не найден (как в вашем случае), элементы списка инициализаторов составляют аргументы для вызова конструктора.
Фактически, единственное отличие прямой инициализации списка и инициализации копирования списка заключено в последнем, выделенном жирным шрифтом предложении.
Это одно из преимуществ инициализации списка: это не требует наличия специальной функции-члена, которая в любом случае не будет использоваться.