decltype 所需的不完整类型的派生到基数转换

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

本文关键字:派生 类型 转换 decltype      更新时间:2023-10-16

我遇到了这个代码,涉及尾随返回类型和继承。

下面的最小示例使用 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 编译。链接到神霹雳 https://godbolt.org/z/qf_k6X .

谁能解释我

  • auto bar() -> decltype(return expression)decltype(auto) bar()有何不同
  • 为什么编译器之间的行为不同
  • 正确的实施是什么?

这是一个gcc 错误,尾随返回类型不在完整的类上下文中 [class.mem]

的完整类上下文是

  • 函数体,
  • 默认参数,
  • noexcept-specifier ([except.spec]),
  • 合同条件,或
  • 默认成员初始值设定项

我们看到,从 [conv.ptr] 派生到基数的转换需要一个完整的类

类型为"指向 cv D 的指针"的 prvalue(其中 D 是完整的类类型)可以转换为类型为"指向 cv B 的指针"的 prvalue,其中 B 是 D 的基类。

和 [dcl.init.ref]

如果可以通过标准转换序列将"指向 cv2 T2 的指针"类型的 prvalue 转换为"指向 cv1 T1 的指针"类型,则"cv1 T1"与"cv2 T2"的引用兼容。在使用两种类型的引用兼容关系来建立引用绑定的有效性并且标准转换序列格式不正确的所有情况下,需要这种绑定的程序都是格式不正确的。

另一方面,函数体位于完整的类上下文中,因此派生到基的转换是格式正确的。包含占位符类型(decltype(auto))的返回类型只要在使用它的表达式之前已经推导出来,它就有效。

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

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

只要你知道你想用Base来称呼它。

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

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

[dcl.fct]/9]

不应在返回或参数类型中定义类型。参数的类型或返回类型 函数定义不应是不完整的类类型(可能是 CV 限定的),除非该函数被删除 (8.4.3)或该定义嵌套在该类的成员规范中(包括定义) 在类中定义的嵌套类中)。

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

另一方面,decltype(auto)工作,因为在需要函数的签名之前,实际上不会推导推导出的返回类型。 在您的情况下,当您在main中调用bar()时会发生这种情况。此时,class Derived是完全定义的类型。叮当说得对。

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