成员函数声明签名中的类成员可见性

Class member visibility in member function declaration signature

本文关键字:成员 可见性 函数 声明      更新时间:2023-10-16

为什么这样做:

template <typename A>
struct S {
A a;
template <typename B>
auto f(B b) ->
decltype(a.f(b))
{
}
};

但这并没有(af交换了位置):

template <typename A>
struct S {
template <typename B>
auto f(B b) ->
decltype(a.f(b))
{
}
A a;
};

表示CCD_ 3未在该范围内声明(在decltype内),但添加显式CCD_。

template <typename A>
struct S {
A a;
template <typename B>
auto f(B b) ->
decltype(a.f(b))
{
}
};

这是因为在尾随返回类型中,周围类的成员是可见的。不是所有成员,而是仅在其之前声明的成员(在尾部返回类型中,类被认为是完整的而不是,而不是函数体)。那么这里要做的是:

  • 当我们在模板中时,会进行查找以查看a是否依赖。由于a是在f之前声明的,因此发现a引用类成员
  • 通过C++中的模板规则,发现a是指当前实例化的成员,因为它是周围模板的实例化的成员。在C++中,这个概念主要用于决定名称是否是依赖的:如果一个名称已知是指周围模板的成员,那么在实例化时就不一定需要查找它,因为编译器已经知道模板的代码(它被用作从中实例化的类类型的基础!)。考虑:

    template<typename T>
    struct A {
    typedef int type;
    void f() {
    type x;
    A<T>::type y;
    }
    };
    

在C++03中,声明y的第二行将是一个错误,因为A<T>::type是一个依赖名称,它前面需要一个typename。只接受第一行。在C++11中,这种不一致性得到了修复,并且两个类型名称都是非依赖的,不需要typename。如果将typedef更改为typedef T type;,那么xy这两个声明都将使用依赖类型,但这两个都不需要typename,因为您仍然命名当前实例化的成员,并且编译器知道您命名了类型。

  • 所以a是当前实例化的一个成员。但它是依赖的,因为用于声明它的类型(A)是依赖的。然而,这在您的代码中并不重要。无论是否依赖,都会找到a并且代码有效

template <typename A>
struct S {
template <typename B>
auto f(B b) ->
decltype(a.f(b))
{
}
A a;
};

在该代码中,再次查找a以查看它是否是依赖的和/或它是否是当前实例化的成员。但是,由于我们在上面了解到,在尾部返回类型之后声明的成员是不可见的,所以我们找不到a的声明。在C++中,除了"当前实例化的成员"这个概念之外,还有另一个概念:

  • 未知专业化的成员。这个概念用于指代这样一种情况,即名称可能指代依赖于模板参数的类的成员。如果我们访问了B::a,那么a将是未知专业化的成员,因为当B在实例化时被替换时,哪些声明将是可见的是未知的。

  • 既不是当前专业的成员,也不是未知专业的成员所有其他名称都是这样您的情况适合这里,因为众所周知,当实例化发生时,a永远不可能是任何实例化的成员(请记住,名称查找找不到a,因为它是在f之后声明的)。

由于a不受任何规则的依赖,因此未找到任何声明的查找是绑定,这意味着实例化时没有其他查找可以找到声明。非依赖名称在模板定义时查找。现在GCC正确地给出了一个错误(但请注意,与往常一样,不需要立即诊断格式错误的模板)。

template <typename A>
struct S {
template <typename B>
auto f(B b) ->
decltype(this->a.f(b))
{
}
A a;
};

在这种情况下,您添加了a0和GCC已接受。再次查找this->之后的名称a,以查看它是否可能是当前实例化的成员。但同样由于尾部返回类型中的成员可见性,所以找不到声明。因此,该名称被认为不是当前实例化的成员。由于在实例化时,S不可能具有a可以匹配的其他成员(S没有依赖于模板参数的基类),因此该名称也不是未知专业化的成员。

同样,C++没有使this->a依赖的规则。然而,它使用this->,因此在实例化时,名称必须引用S的某个成员!所以C++标准说

类似地,如果对象表达式的类型为当前实例化的类成员访问表达式中的id表达式没有引用当前实例化的成员或未知专业化的成员,则即使包含成员访问表达式的模板没有实例化,程序也会格式错误;无需诊断。

同样,此代码不需要进行诊断(GCC实际上没有给出诊断)。成员访问表达式this->0中的id表达式a在C++03中是依赖的,因为该标准中的规则不像C++11中那样详细和微调。让我们暂时想象一下C++03有decltype和尾随返回类型。这意味着什么?

  • 查找将延迟到实例化,因为this->a将是依赖的
  • 实例化S<SomeClass>时的查找将失败,因为在实例化时找不到this->a(正如我们所说,后面的返回类型看不到稍后声明的成员)

因此,C++11对该代码的早期拒绝是好的,也是有用的。

成员函数的主体被编译,就好像它是在类之后定义的一样。因此,类中声明的所有内容此时都在作用域中。

但是,函数的声明仍然在类声明中,并且只能看到前面的名称

template <typename A>
struct S {
template <typename B>
auto f(B b) ->
decltype(a.f(b)); // error - a is not visible here
A a;
};
template <typename A>
template <typename B>
auto S<A>::f(B b) ->
decltype(a.f(b))
{
return a.f(b);   // a is visible here
}

标准规定(第14.6.2.1节):

如果对于给定的模板参数集,实例化了一个模板的专门化,该模板引用了具有限定id或类成员访问表达式的当前实例化的成员,则在模板实例化上下文中查找限定id或类别成员访问表达式中的名称。

this->a是一个类成员访问表达式,因此该规则适用,并且在实例化时进行查找,其中S<A>是完整的


最后,这根本不能解决你的问题,因为第5.1.1节说:

如果声明声明了类X的成员函数或成员函数模板,表达式this是介于可选cv限定符seq函数定义成员声明符声明符结束之间的类型为"指向cv限定符seqX的指针"的prvalue。它不得出现在可选的cv限定符seq之前,也不得出现在静态成员函数的声明中(尽管其类型和值类别在静态成员功能中定义为在非静态成员功能内定义)。

因此不能在此处使用this->,因为它位于函数声明的cv限定符seq部分之前。

等等,不,不是!第8.4.1节规定

函数定义中的声明符应具有格式

D1 (parameter-declaration-clause)cv-qualifier-seq opt ref-qualifier opt exception-specification opt attribute-specifier-seq opt trailing-return-type opt