decltype 要求的不完整类型的派生到基转换

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

我发现这段代码被剪掉了,涉及尾随 return 类型和继承。

下面的最小示例可以用 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()(c++14 扩展),代码也会使用 clang 进行编译。 Link 神马 https://godbolt.org/z/qf_k6X .

谁能给我解释一下

我认为 Clang 拒绝这个是错误的:

关于函数定义的return类型,C++14标准是这样说的:

[dcl.fct]/9]

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).

在您的示例中,bar 的定义嵌套在 class Derived 的成员规范中。所以这是允许的,GCC、ICC 和 MSVC 做对了。

另一方面,decltype(auto) 之所以有效,是因为推导的 return 类型在需要函数签名之前实际上并未推导。 在您的情况下,当您在 main 中调用 bar() 时,就会发生这种情况。那时 class Derived 是一个完全定义的类型。 Clang 做对了。

请注意,即使使用 auto 而不是 decltype(auto) 也适用于您的示例。参见 Godbolt 上的 Demo

这是一个 gcc 错误,尾随的 return 类型不在 complete-class 上下文中 [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

我们看到从 [conv.ptr]

派生到基础的转换需要一个完整的 class

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.

[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.

另一方面,函数体位于 complete-class context 中,因此派生到基础的转换是良构的。 return 类型涉及占位符类型 (decltype(auto)) is valid 只要它在使用它的表达式之前已经被推导。

对于 C++11 中可能的解决方法,您可以使用

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

前提是您知道要用 Base 调用它。