Ответ 1
Где дыра в моем понимании?
tlTL;DR; Никто не понимает инициализацию.
TL;DR; Инициализация списков предпочитает конструкторы std::initializer_list<T>
, но не возвращается к инициализации без списка. Он возвращается к рассмотрению конструкторов. Инициализация без списка будет рассматривать функции преобразования, но резервный нет.
Все правила инициализации взяты из [dcl.init]. Поэтому давайте просто перейти от первых принципов.
- Если инициализатор представляет собой (не заключенный в скобки) бит-init-list или is = braced-init-list, объект или ссылка инициализируются списком.
Первая первая маркерная точка охватывает любую инициализацию списка. Это перескакивает X x{y}
и X x = {y}
на [dcl.init.list]. Мы вернемся к этому. Другой случай проще. Посмотрим на X x = y
. Мы прямо говорим:
- В противном случае (например, для остальных случаев инициализации копии) пользовательские последовательности преобразования, которые могут преобразовываться из типа источника в тип назначения или (когда используется функция преобразования) в его производный класс, перечисляются, как описано в [over.match.copy], а лучший выбирается с помощью разрешения перегрузки.
Кандидатами в [over.match.copy] являются:
- Конструкторы преобразования
T
[в нашем случаеX
] являются функциями-кандидатами.- Когда тип выражения инициализатора является типом класса "cv
S
", рассматриваются неявные функции преобразованияS
и его базовые классы.В обоих случаях список аргументов имеет один аргумент, который является выражением инициализатора.
Это дает нам кандидатов:
X(Y const &); // from the 1st bullet
Y::operator X(); // from the 2nd bullet
Второй эквивалент имеет X(Y& )
, так как функция преобразования не соответствует критериям cv. Это приводит к меньшей ссылочной ссылке, чем конструктор преобразования, поэтому он предпочтет. Обратите внимание: здесь нет ссылки X(X&& )
на С++ 17.
Теперь вернемся к случаям инициализации списка. Первая соответствующая маркерная точка [dcl.init.list]/3.6:
В противном случае, если
T
- тип класса, рассматриваются конструкторы. Соответствующие конструкторы перечислены, и лучший выбирается с помощью разрешения перегрузки ([over.match], [over.match.list]). Если для преобразования любого из аргументов требуется сужение преобразования (см. Ниже), программа плохо сформирована.
который в обоих случаях приводит нас к [over.match.list], который определяет разрешение двухфазной перегрузки:
- Изначально функции-кандидаты - это конструкторы-инициализаторы-списки ([dcl.init.list]) класса T, а список аргументов состоит из списка инициализаторов как один аргумент.
- Если не найден жизнеспособный конструктор списка инициализаторов, разрешение перегрузки выполняется снова, где функции-кандидаты - все конструкторы класса T, а список аргументов состоит из элементов списка инициализаторов.
Если в списке инициализаторов нет элементов, а T имеет конструктор по умолчанию, первая фаза будет опущена. В инициализации списка копий, если выбран явный конструктор, инициализация плохо сформирована.
Кандидаты являются конструкторами X
. Единственное различие между X x{y}
и X x = {y}
заключается в том, что если последний выбирает конструктор explicit
, инициализация плохо сформирована. У нас даже нет конструкторов explicit
, поэтому они эквивалентны. Следовательно, мы перечисляем наши конструкторы:
-
X(Y const& )
-
X(X&& )
черезY::operator X()
Первый - это прямая ссылка, которая является Точным соответствием. Последнее требует пользовательского преобразования. Следовательно, в этом случае мы предпочитаем X(Y const& )
.
Обратите внимание, что gcc 7.1 ошибается в режиме С++ 1z, поэтому я зарегистрировал ошибка 80943.