Скопируйте инициализацию формы ‘= {}’

Учитывая следующее:

#include <stdio.h>

class X;

class Y
{
public:
Y() { printf("  1\n"); }             // 1
// operator X(); // 2
};

class X
{
public:
X(int) {}
X(const Y& rhs) { printf("  3\n"); } // 3
X(Y&& rhs) { printf("  4\n"); }      // 4
};

// Y::operator X() { printf("   operator X() - 2\n"); return X{2}; }

int main()
{
Y y{};     // Calls (1)

printf("j\n");
X j{y};    // Calls (3)
printf("k\n");
X k = {y}; // Calls (3)
printf("m\n");
X m = y;   // Calls (3)
printf("n\n");
X n(y);    // Calls (3)

return 0;
}

Все идет нормально. Теперь, если я включу оператор преобразования Y::operator X()Я понял это; —

  X m = y; // Calls (2)

Насколько я понимаю, это происходит потому, что (2) является «менее постоянным», чем (3) и
поэтому предпочтительнее. Призыв к X конструктор исключен

Мой вопрос, почему не определение X k = {y} изменить свое поведение таким же образом? я знаю это = {} технически «инициализация копии списка», но в отсутствие конструктора, принимающего initializer_list типа, разве это не возвращает к «копированию инициализации»? то есть — так же, как и для X m = y

Где дыра в моем понимании?

14

Решение

Где дыра в моем понимании?

tltldr; Никто не понимает инициализацию.

tldr; Предпочитает инициализацию списка std::initializer_list<T> конструкторы, но это не отступает от не-списочной инициализации. Это только возвращается к рассмотрению конструкторов. При инициализации без списков учитываются функции преобразования, а в качестве альтернативы — нет.


Все правила инициализации происходят из [Dcl.init]. Итак, давайте просто перейдем от первых принципов.

[Dcl.init] /17.1:

  • Если инициализатор (без скобок) приготовился-INIT-лист или есть = приготовился-INIT-лист, объект или ссылка инициализируются списком.

Первый первый пункт относится к любой инициализации списка. Это прыгает X x{y} а также X x = {y} к [Dcl.init.list]. Мы вернемся к этому. Другой случай проще. Давайте посмотрим на X x = y, Мы звоним прямо в:

[Dcl.init] /17.6.3:

  • В противном случае (то есть для остальных случаев инициализации копирования) определяемые пользователем последовательности преобразования, которые могут преобразовывать из типа источника в тип назначения или (когда используется функция преобразования) в его производный класс, перечисляются, как описано в [Over.match.copy], и лучший выбирается через разрешение перегрузки.

Кандидаты в [over.match.copy] являются:

  • Конвертирующие конструкторы T [в нашем случае, X] являются кандидатами функций.
  • Когда тип выражения инициализатора является типом класса «резюме S”, Неявные функции преобразования S и его базовые классы рассматриваются.

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

Это дает нам кандидатов:

X(Y const &);     // from the 1st bullet
Y::operator X();  // from the 2nd bullet

2-й эквивалентно тому, чтобы иметь X(Y& ), поскольку функция преобразования не является cv-квалифицированной. Это делает ссылку менее квалифицированной по cv, чем конструктор преобразования, так что это предпочтительнее. Обратите внимание, что нет вызова X(X&& ) здесь, в C ++ 17.


Теперь вернемся к случаям инициализации списка. Первая соответствующая точка [Dcl.init.list] /3.6:

В противном случае, если T является типом класса, конструкторы рассматриваются. Применимые конструкторы перечисляются, и лучший выбирается через разрешение перегрузки ([over.match], [over.match.list]). Если для преобразования какого-либо из аргументов требуется сужающее преобразование (см. Ниже), программа является некорректной.

что в обоих случаях приводит нас к [Over.match.list] который определяет двухфазное разрешение перегрузки:

  • Первоначально функции-кандидаты являются конструкторами списка инициализаторов ([dcl.init.list]) класса T, а список аргументов состоит из списка инициализаторов как единственного аргумента.
  • Если жизнеспособный конструктор списка инициализаторов не найден, снова выполняется разрешение перегрузки, где все функции-кандидаты являются конструкторами класса T, а список аргументов состоит из элементов списка инициализатора.

Если список инициализаторов не имеет элементов, а T имеет конструктор по умолчанию, первая фаза опускается. При инициализации copy-list-init, если выбран явный конструктор, инициализация некорректна.

Кандидаты являются конструкторами X, Единственная разница между X x{y} а также X x = {y} в том, что если последний выбирает explicit конструктор, инициализация некорректна. У нас даже нет explicit конструкторы, так что оба эквивалентны. Следовательно, мы перечисляем наши конструкторы:

  • X(Y const& )
  • X(X&& ) в порядке Y::operator X()

Первый — это прямая ссылка, которая является точным соответствием. Последнее требует пользовательского преобразования. Следовательно, мы предпочитаем X(Y const& ) в этом случае.


Обратите внимание, что gcc 7.1 делает это неправильно в режиме C ++ 1z, поэтому я подал ошибка 80943.

7

Другие решения

Мой вопрос: почему определение X k = {y} не меняет свое поведение таким же образом?

Потому что, концептуально говоря, = { .. } является инициализация за то, что автоматически выбирает «лучший» способ инициализации цели из брекетов, в то время как = value это также инициализация, но концептуально также преобразование значение к другому значению. Преобразование является полностью симметричным: If изучит исходное значение, чтобы увидеть, предоставляет ли он способ создания цели, и изучит цель, чтобы увидеть, предоставляет ли он способ принятия источника.

Если ваш целевой тип struct A { int x; } затем с помощью = { 10 } не будет пытаться преобразовать 10 в A (который потерпит неудачу). Но он будет искать лучшую (с их точки зрения) форму инициализации, которая здесь составляет совокупную инициализацию. Однако если A не является агрегатом (добавить конструкторы), тогда он будет вызывать конструкторы, где в вашем случае он находит Y принимаются с готовностью без необходимости конвертации. Там нет такой симметрии между источником и целью, как это происходит с преобразованием при использовании = value форма.

Ваше подозрение о «менее постоянном» функции преобразования совершенно верно. Если вы сделаете функцию преобразования постоянным членом, то она станет неоднозначной.

0

По вопросам рекламы [email protected]