在一个类的内部,为什么 `auto b() -> decltype(a()) {}` 能够工作,但是 `decltype(a()) b() {}` 却不能?

21

考虑以下代码:(Ideone)

struct S
{
    int a() {return 0;}
    decltype(a()) b() {return 1;}
};

出现以下错误:

错误:无法在没有对象的情况下调用成员函数'int S::a()'


另一方面,此代码可以成功编译:(Ideone)

struct S
{
    int a() {return 0;}
    auto b() -> decltype(a()) {return 1;}
};


为什么一个例子能运行,而另一个例子无法编译?

在这两个例子中,编译器的行为是否完全正确?

如果编译器是正确的,那么为什么标准规定了这种奇怪的行为?


1
对于那些想知道这是哪个编译器的人:看起来像是gcc,尽管gcc和clang都拒绝第一个并接受第二个。 - jaggedSpire
2个回答

18

a()是非静态成员函数,因此被解释为(*this).a()。引用自[expr.prim.general]/3的一部分:

如果一个声明声明了类X的成员函数或成员函数模板,则表达式this是一个类型为“指向cv限定符序列X”的prvalue,在可选的cv限定符序列和函数定义、成员声明符或声明符的结尾之间。它不应出现在可选的cv限定符序列之前,并且不应出现在静态成员函数的声明中(尽管其类型和值种类在静态成员函数中的定义与它们在非静态成员函数中相同)。

由于S :: b没有cv限定符,所以trailing-return-type出现在可选的cv-qualifier-seq之后(在您的示例中省略),因此this可以出现在那里,但它不能出现在之前。


10
这是因为在查看cv限定符序列之前,您不知道this的类型。 - T.C.
此外,从技术上讲,“a() => (*this).a()”转换从未发生过 - 它只会在“可以使用this”的上下文中发生 - 然后它会破坏[expr.prim.id]/2 - T.C.
@HolyBlackCat 是的,但这是因为语言设计者爱你,并希望尽可能地赋予你使用 this 的自由 :) - Brian Bi

10

@Brian的回答进行一些补充:

  1. 在第一个例子中,a()并没有被转换为 (*this).a()。这种转换是在[class.mfct.non-static]/3中指定的,并且仅在“可以使用this”的上下文中发生。如果没有这种转换,由于违反[expr.prim.id]/2,代码将是非法的:

    表示类的非静态数据成员或非静态成员函数的id-expression只能在以下情况下使用:

    • 作为类成员访问的一部分([expr.ref]),其中对象表达式引用该成员的类63或从该类派生的类,或

    • 形成成员指针([expr.unary.op]),或

    • 如果该id-expression表示非静态数据成员,并且出现在未求值的操作数中。

    使用表示非静态成员函数的id-expression a,超出了允许的上下文范围。

  2. 没有进行类成员访问转换是重要的原因是,它使以下代码有效:

    struct A {
        int a;
        decltype(a) b();
    };
    

    如果将decltype(a)改为decltype((*this).a),那么代码就无法通过编译。

  3. *this在类成员访问中有一个特殊例外,即使其类型不完整也可以使用([expr.prim.this]/2):

    与其他情况不同,对于类成员的访问而言,在成员函数体之外访问时,对象表达式*this并不要求具有完整的类型。([expr.ref])


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接