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

时间:2012-12-21 16:32:41

标签: c++ c++11 decltype trailing-return-type

为什么这样做:

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;
};

a未在该范围内声明(在decltype内),但添加显式this->使其有效。

3 个答案:

答案 0 :(得分:4)

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 ++中,除了当前实例化的概念#34;还有另一个概念:

  • 未知专业化的成员。此概念用于指代名称可能改为引用依赖于模板参数的类成员的情况。如果我们访问了B::a,则a将成为未知专业化的成员,因为 unknown B替换为a时,将显示哪些声明实例。

  • 既不是当前成员,也不是未知专业成员。所有其他名称都是如此。 您的案例适合,因为已知a在实例化发生时永远不能成为任何实例化的成员(请记住名称查找无法找到f,因为它已声明在a之后。

由于template <typename A> struct S { template <typename B> auto f(B b) -> decltype(this->a.f(b)) { } A a; }; 不依赖于任何规则,因此未找到任何声明的查找是绑定,这意味着在实例化中没有可以找到声明的其他查找。在模板定义时查找非依赖名称。现在GCC正确地给你一个错误(但请注意,一如既往,不需要立即诊断出错误的模板。)


this

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

C ++再一次没有让this->依赖的规则。但是它使用S,因此名称​​必须在实例化时引用a的某个成员!所以C ++标准说

  

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

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

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

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

答案 1 :(得分:2)

成员函数的主体被编译为好像它是在类之后定义的。因此,在该类中声明的所有内容都在此范围内。

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

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
    }

答案 2 :(得分:2)

标准说(第14.6.2.1节):

  

如果对于给定的一组模板参数,实例化了一个模板的特化,该模板的特殊化引用了具有qualified-id或类成员访问表达式的当前实例化的成员,那么在qualified-id或class成员中的名称在模板实例化上下文中查找访问表达式。

this->a是类成员访问表达式,因此应用此规则并在实例化时进行查找,其中 S<A>已完成


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

  

如果声明声明了类X的成员函数或成员函数模板,表达式this是类型为“指向 cv-qualifier-seq {的指针的prvalue {1}}“在可选的 cv-qualifier-seq 功能定义的末尾,成员声明符声明符 即可。它不会出现在可选的 cv-qualifier-seq 之前   它不应出现在静态成员函数的声明中(尽管它的类型和值类别是在静态成员函数中定义的,因为它们在非静态成员函数中)。

所以你不能在这里使用X,因为它位于函数声明的 cv-qualifier-seq 部分之前。

等等,不,不是!第8.4.1节说

  

function-definition 中的声明者应具有

形式      

this-> D1 ( parameter-declaration-clause )