Derived-to-base conversion for incomplete types required by decltype

This is a gcc bug, the trailing return type isn't within a complete-class context [class.mem]

A complete-class context of a class is a

  • function body,
  • default argument,
  • noexcept-specifier ([except.spec]),
  • contract condition, or
  • default member initializer

We see that a complete class is needed for the derived to base conversion from [conv.ptr]

A prvalue of type “pointer to cv D”, where D is a complete class type, can be converted to a prvalue of type “pointer to cv B”, where B is a base class of D.

and [dcl.init.ref]

“cv1 T1” is reference-compatible with “cv2 T2” if a prvalue of type “pointer to cv2 T2” can be converted to the type “pointer to cv1 T1” via a standard conversion sequence. In all cases where the reference-compatible relationship of two types is used to establish the validity of a reference binding and the standard conversion sequence would be ill-formed, a program that necessitates such a binding is ill-formed.

On the other hand, a function body is within a complete-class context and thus the derived to base conversion is well-formed. The return type involving a placeholder type (decltype(auto)) is valid as long as it is already deduced before an expression using it.

For a possible workaround in C++11, you may use

auto bar() -> decltype(foo(std::declval<Base&>()))
    return foo(*this);

provided you know that you want to call it with Base.

I think Clang is wrong to reject this:

Regarding the return type of a function definition, the C++14 standard says this:


Types shall not be defined in return or parameter types. The type of a parameter or the return type for a function definition shall not be an incomplete class type (possibly cv-qualified) unless the function is deleted (8.4.3) or the definition is nested within the member-specification for that class (including definitions in nested classes defined within the class).

In your example the definition of bar is nested within the member-specification of the class Derived. So this is allowed and GCC, ICC and MSVC get this right.

On the other hand decltype(auto) works because a deduced return type is not actually deduced until the signature of the function is needed. And in your case, this happens when you call bar() in main. At that point of time the class Derived is a completely defined type. Clang gets this right.

Note that even using auto instead of decltype(auto) will work for your example. See Demo on godbolt.