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

时间:2019-04-26 03:04:47

标签: c++ inheritance language-lawyer 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()(c ++ 14扩展名),代码也会使用clang进行编译。链接到Godbolt https://godbolt.org/z/qf_k6X

谁能解释我

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

2 个答案:

答案 0 :(得分:5)

这是一个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) is valid,只要在使用它的表达式之前已经推断出它即可。

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

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

前提是您知道要使用Base进行调用。

答案 1 :(得分:1)

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

关于函数定义的返回类型,C ++ 14标准对此表示:

[dcl.fct]/9]

  

类型不得在返回或参数类型中定义。参数的类型或返回类型   除非删除该函数(8.4.3)或该定义嵌套在该类的成员规范内,否则该函数定义不得为不完整的类类型(可能具有cv限定)   在该类中定义的嵌套类中)。

在您的示例中,bar的定义嵌套在class Derived的成员规范内。因此,这是允许的,GCC,ICC和MSVC可以做到这一点。

另一方面,decltype(auto)之所以起作用,是因为直到需要该函数的签名才实际推断出推导的返回类型。 在您的情况下,当您在bar()中调用main时会发生这种情况。在那时,class Derived是一个完全定义的类型。 lang声是正确的。

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