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

Нестандартный черновик n3376 имеет в качестве примера (12.3.2: 2) использование функции явного преобразования в определяемый пользователем тип:

class Y { };
struct Z {
explicit operator Y() const;
};
void h(Z z) {
Y y1(z); // OK: direct-initialization
}

Согласно 12.3.2: 2, явная функция преобразования:рассматривается только как пользовательское преобразование для прямой инициализации«; однако, это могло бы позволить:

struct Y { Y(int); };
struct Z {
explicit operator int() const;
};
void h(Z z) {
Y y1(z); // direct-initialization
}

который, кажется, вступает в противоречие с целью стандарта, и действительно отклоняется gcc-4.7.1:

source.cpp: In function 'void h(Z)':
source.cpp:4:9: error: no matching function for call to 'Y::Y(Z&)'
source.cpp:4:9: note: candidates are:
source.cpp:1:12: note: Y::Y(int)
source.cpp:1:12: note:   no known conversion for argument 1 from 'Z' to 'int'
source.cpp:1:8: note: constexpr Y::Y(const Y&)
source.cpp:1:8: note:   no known conversion for argument 1 from 'Z' to 'const Y&'
source.cpp:1:8: note: constexpr Y::Y(Y&&)
source.cpp:1:8: note:   no known conversion for argument 1 from 'Z' to 'Y&&'

Правильно ли gcc отклонить преобразование из Z в Y с помощью intили стандарт действительно разрешает такое использование?

Я рассмотрел контекст упомянутого прямая инициализация; согласно определению прямой инициализации для типа класса в 8.5: 16, конструктор вызывается с выражением инициализатора в качестве аргументов, которые поэтому преобразуются в тип параметра с помощью неявной последовательности преобразования (13.3.3.1). Поскольку последовательность неявного преобразования является неявным преобразованием (4: 3) и, таким образом, моделирует инициализацию копирования (8.5: 14), а не прямую инициализацию, язык в 12.3.2: 2 должен ссылаться на выражение в целом.

Также обратите внимание, что это не является нарушением 12.3: 4 (несколько пользовательских преобразований); тот же компилятор доволен тем же кодом с explicit удалены (как Clang и Comeau):

struct Y { Y(int); };
struct Z { operator int(); };
void h(Z z) {
Y y1(z); // direct-initialization
}

Я думаю, что Джесси Гуд определил различие между operator Y а также operator int случаи в 13.3.1.4:1, но есть третий случай, который меня все еще касается:

struct X {};
struct Y { Y(const X &); };
struct Z {
explicit operator X() const;
};
void h(Z z) {
Y y1(z); // direct-initialization via class-type X
}

Инициализация временного X быть связанным с единственным const X & параметр конструктора Y продолжается в контексте прямой инициализации в соответствии с 13.3.1.4:1, с T как X а также S как Z, Я думаю, что этот пункт неверен и должен читать:

13.3.1.4 Инициализация копии класса с помощью пользовательского преобразования [over.match.copy]

1 — […] При инициализации временной привязки к первому параметру
конструктора, который принимает ссылку на возможно резюме-квалифицированный T в качестве первого аргумента, вызывается с одним аргументом в контексте прямой инициализации объекта типа «cv2 T«, явные функции преобразования также рассматриваются. […]

Во избежание путаницы, я думаю, что 12.3.2: 2 также следует изменить:

12.3.2 Функции преобразования [class.conv.fct]

2 — Функция преобразования может быть явной (7.1.2), и в этом случае она рассматривается только как пользовательское преобразование для прямой инициализации (8.5) в определенных контекстах (13.3.1.4, 13.3.1.5, 13.3.1.6). […]

Любые комментарии по поводу выше?

3

Решение

Как отмечено в ответе Люка Дантона, неявное преобразование определяется в терминах инициализации копирования. Затем, если мы посмотрим на 13.3.1.4:1[Copy-initialization класса путем пользовательского преобразования]:

Когда типом выражения инициализатора является тип класса «cv S»,
неявные функции преобразования S и его базовые классы
считается. При инициализации временного привязывается к первому
параметр конструктора, который принимает ссылку на возможно
c-квалифицированный T в качестве первого аргумента вызывается с одним аргументом в
контекст прямой инициализации, явные функции преобразования
также рассматриваются. Те, которые не спрятаны в S и дают
тип которого cv-неквалифицированная версия является тот же тип, что и T или является
его производный класс является функциями-кандидатами
. Функции преобразования
которые возвращают «ссылку на X», возвращают lvalues ​​или xvalues, в зависимости от
тип ссылки типа X и, следовательно, считается, чтобы дать
X для этого процесса выбора функций-кандидатов.

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

По поводу третьего дела:

После нахождения Дефект 1087, Кажется очевидным, что намерение состояло в том, чтобы разрешать, копировать, перемещать и конструировать шаблоны при прямой инициализации объекта cv2 T, как вы упомянули. Если вы читаете первый отрывок из 13.3.1.4, он говорит Assuming that “cv1 T” is
the type of the object being initialized, with T a class type
так что я думаю, что подразумевает of an object of type "cv2 T" что вы упоминаете.
Однако (после прочтения) кажется, что изменение из-за отчета о дефектах привело к тому, что формулировка стала расплывчатой ​​и не охватывала третий предложенный вами случай.

3

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

Согласно 8.5 и 13.3.1.3 конструкторы Y считаются, и лучший выбирается через разрешение перегрузки. В этом случае соответствующие конструкторы Y(int); и копировать и перемещать конструкторы. В процессе разрешения перегрузки 13.3.2 Viable functions [over.match.viable] определяет это:

3 секунды, для F чтобы быть жизнеспособной функцией, должен существовать для каждого аргумента неявная последовательность преобразования (13.3.3.1), который преобразует этот аргумент в соответствующий параметр F, […]

Для всех этих конструкторов нет такого преобразования из Z либо int или один из ароматов Y, Чтобы убедиться сами, давайте рассмотрим, что в стандарте говорится о последовательностях неявного преобразования в 13.3.3.1 Последовательности неявного преобразования [over.best.ics]:

1 Последовательность неявного преобразования — это последовательность преобразований, используемая для преобразования аргумента в вызове функции в тип соответствующего параметра вызываемой функции. Последовательность преобразований является неявным преобразованием, как определено в разделе 4, что означает, что оно регулируется правилами для инициализации объекта или ссылки одним выражением (8.5, 8.5.3).

Если мы дадим перекрестную ссылку на пункт 4, то узнаем, что неявное преобразование определяется в терминах инициализации копирования (т.е. T t=e;, где T является int а также e является z):

(§4.3) Выражение e может быть неявно преобразовано в тип T тогда и только тогда, когда объявление T t=e; является корректным для некоторой изобретенной временной переменной t (8.5). […]

Поэтому я беру 12.3.2: 2, чтобы не подать заявку на этот инициализация, которая происходит в более широком контексте прямой инициализации. В противном случае это противоречило бы этому последнему пункту.

4

Я не являюсь юристом по языку, однако формулировка стандарта подразумевает, что маркировка оператора преобразования как explicit требует, чтобы вы явно указали тип конверсии (т.е. int) как часть инициализации объекта y1, С кодом Y y1(z)может показаться, что вы полагаетесь на неявное преобразование, так как тип, который вы указываете для переменной y1 является Y,

Следовательно, я ожидаю, что правильное использование оператора явного преобразования в этой ситуации будет:

Y y1( int(z) );

Или, поскольку вы эффективно указываете бросок, желательно

Y y1( static_cast<int> (z) );
2
По вопросам рекламы [email protected]