Следующий код прекрасно компилируется с g++ (GCC) 4.7.1 20120721
, но
терпит неудачу с недавней сборкой clang version 3.2 (trunk)
,
struct Y {};
struct X {
operator const Y() const { return Y(); }
};
void f(Y&& y) {}
int main()
{
f(X());
return 0;
}
Изменение оператора преобразования на operator Y() const
достаточно
чтобы код компилировался на обоих компиляторах.
Какой компилятор на самом деле соответствует стандарту в этом случае? Что значит
на самом деле стандарт говорит об этом?
Дословная ошибка в соответствии с просьбой:
bla.cpp:14:5: error: no viable conversion from 'X' to 'Y'
f(X());
^~~
bla.cpp:1:8: note: candidate constructor (the implicit copy constructor) not viable: no known conversion from 'X' to
'const Y &' for 1st argument
struct Y {
^
bla.cpp:1:8: note: candidate constructor (the implicit move constructor) not viable: no known conversion from 'X' to
'Y &&' for 1st argument
struct Y {
^
bla.cpp:6:3: note: candidate function
operator const Y() const { return Y(); }
^
bla.cpp:10:12: note: passing argument to parameter 'y' here
void f(Y&& y) {}
^
РЕДАКТИРОВАТЬ: К сожалению даже добавление перегрузки
void f(const Y&) {}
по-прежнему заставляет Clang выбрать эталонную перегрузку rvalue, и поэтому
ломает существующий код, который использовался для компиляции, например, со стандартным
контейнеры.
Я считаю, что Clang прав, чтобы отвергнуть это. Передача аргумента f(Y&&)
требует двух шагов преобразования, первый из которых ваш operator const Y()
а второе существо Y
Копируй конструктор. Я считаю, что оба они считаются пользовательскими преобразованиями, и оба являются неявными, что нарушает принцип, согласно которому последовательность неявных преобразований включает только одно пользовательское преобразование.
это Цель возврата по постоянному значению? содержит некоторые интересные идеи о семантике возвращения const T
,
Хм, если я попытаюсь добавить перегрузку void f(const Y&y)
как сейчас отредактированный вопрос, лязг ведет себя довольно странно. Он все еще жалуется на невозможность конвертировать X
в Y
и даже не перечисляет перегрузку f(const Y& y)
в его диагностике. Но как только я меняю перегрузку на Y
по значению, то есть написать void f(const Y y)
жалуется на звонок f
быть неоднозначным
Это с лязг XCode 4.5, который сообщает Apple clang version 4.1 (tags/Apple/clang-421.11.66) (based on LLVM 3.1svn)
, Если вы можете воспроизвести это с помощью ванильного лязга, вы, вероятно, должны сообщить об этом в списке рассылки clang — наверняка есть ошибка, скрывающаяся там где-то…
Пример плохо сформирован.
Некоторые цитаты N3242.
8.5.3 пункт 4:
Данные типы »CV1
T1
» а также «CV2T2
, «»CV1T1
«Связан со ссылкой на»CV2T2
» еслиT1
тот же тип, что иT2
, или жеT1
это базовый классT2
, «CV1T1
«Является справочно-совместимым с»CV2T2
» еслиT1
имеет отношение кT2
а также CV1 является той же квалификацией cv или большей квалификацией, чем CV2.
пункт 5 (метки от пуль мои):
Ссылка на тип «CV1
T1
«инициализируется выражением типа»CV2T2
» следующее:
- Если ссылка является ссылкой lvalue и …
В противном случае ссылка должна быть ссылкой lvalue на энергонезависимый тип const (т.е. CV1 должен быть
const
), или ссылка должна быть ссылкой.а. Если выражение инициализатора
- я. это xvalue, класс prvalue, массив prvalue или функция lvalue и «CV1
T1
«является справочно-совместимым с»CV2T2
«, или же- II. имеет тип класса (т.е.
T2
это тип класса), гдеT1
не имеет отношения кT2
и может быть неявно преобразовано в xvalue, класс prvalue или функцию lvalue типа «CV3T3
«, где «CV1T1
«является справочно-совместимым с»CV3T3
»,- тогда ссылка связана с ….
б. В противном случае, временный тип «CV1
T1
«создается и инициализируется из выражения инициализатора с использованием правил неинициализированной копии-инициализации (8.5). Ссылка затем привязывается к временной.T1
имеет отношение кT2
, CV1 должна быть той же квалификации, что и квалификация, или большей, чем CV2. …
Для инициализации параметра функции, T1
является Y
, T2
является X
, а также CV1 а также CV2 оба пусты. 1 отсутствует: ссылка является rvalue ссылкой, а не lvalue ссылкой. 2.a.i. снаружи: X
не совместим с Y
, 2.b. из-за копирования инициализации Y
от значения типа X
включает в себя два пользовательских преобразования: функция преобразования, а затем конструктор копирования Y
, (Точный запрет в 13.3.3.1p4.)
Для случая 2.a.ii. очевидный выбор дляCV3 T3
» является const Y
, но это не хорошо, потому что Y
не совместим с const Y
, Вы можете поспорить за попытку T3
является Y
а также CV3 пусто, но тогда вы вернулись к необходимости конструктора копирования Y
в качестве второго неявного пользовательского преобразования.