Преобразование из базы данных в неполные типы, необходимые для decltype
Я наткнулся на этот фрагмент кода, включающий конечные типы возврата и наследование.
Следующий минимальный пример прекрасно компилируется с g++, а не с clang
struct Base {};
int foo(Base&) {
return 42;
}
struct Derived : public Base {
auto bar() -> decltype(foo(*this)) {
return foo(*this);
}
};
int main()
{
Derived derived;
derived.bar();
return 0;
}
Однако если мы изменим auto bar() → decltype(foo(*this))
на decltype(auto) bar()
(расширение С++ 14), код также будет компилироваться с помощью clang. Ссылка на Godbolt https://godbolt.org/z/qf_k6X.
Может кто-нибудь объяснить мне
- чем
auto bar() → decltype(return expression)
отличается от decltype(auto) bar()
- почему поведение между компиляторами отличается
- что такое правильная реализация?
Ответы
Ответ 1
Это ошибка gcc, конечный тип возврата не находится в контексте полного класса [class.mem]
Полный класс класса является
- функциональное тело,
- аргумент по умолчанию,
- noexcept-спецификатор ([кроме .spec]),
- условие договора или
- инициализатор элемента по умолчанию
Мы видим, что для преобразования из производного в базовый из [conv.ptr] необходим полный класс
Значение типа "указатель на cv D", где D - полный тип класса, может быть преобразовано в значение типа "указатель на cv B", где B - базовый класс D.
и [dcl.init.ref]
"cv1 T1" совместим со ссылками с "cv2 T2", если значение типа "указатель на cv2 T2" можно преобразовать в тип "указатель на cv1 T1" через стандартную последовательность преобразования. Во всех случаях, когда эталонно-совместимые отношения двух типов используются для установления достоверности эталонной привязки, а стандартная последовательность преобразования будет некорректной, программа, для которой требуется такая привязка, некорректна.
С другой стороны, тело функции находится в контексте полного класса, и, следовательно, преобразование из производной в базовую форму является корректным. Тип возврата, включающий тип заполнителя (decltype(auto)
) , действителен, если он уже выведен до выражения, использующего его.
Для возможного обходного пути в С++ 11 вы можете использовать
auto bar() -> decltype(foo(std::declval<Base&>()))
{
return foo(*this);
}
при условии, что вы знаете, что хотите позвонить с помощью Base
.
Ответ 2
Я думаю, что Clang не прав, чтобы отклонить это:
Что касается типа возврата определения функции, стандарт С++ 14 гласит:
[dcl.fct]/9]
Типы не должны быть определены в возвращаемых или типах параметров. Тип параметра или возвращаемого типа для определения функции не должен быть неполным типом класса (возможно, cv-квалифицированным), если только функция не удалена (8.4.3) или определение не вложено в спецификацию члена для этого класса ( включая определения во вложенных классах, определенных в классе).
В вашем примере определение bar
вложено в спецификацию члена class Derived
. Так что это разрешено, и GCC, ICC и MSVC получают это право.
С другой стороны, decltype(auto)
работает, потому что выведенный тип возврата фактически не выводится до тех пор, пока не понадобится подпись функции. И в вашем случае это происходит, когда вы вызываете bar()
в main
. В этот момент class Derived
является полностью определенным типом. Clang понимает это правильно.
Обратите внимание, что даже использование auto
вместо decltype(auto)
будет работать для вашего примера. Посмотрите Демо на Godbolt.
Ответ 3
Derived
является неполным типом до };
достигается, поэтому Clang правильно отклоняет этот код, потому что неполный тип не может быть неявно преобразован в ссылку на полный тип. Чтобы сделать этот код действительным, вы можете использовать decltype(auto)
или просто auto
в качестве возвращаемого типа. Это немного отличается, хотя. Это похоже на ошибку GCC. Я верю, что языковой адвокат скоро приедет и процитирует соответствующие разделы стандарта!