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

yur*_*hek 3 c++ decltype c++11 trailing-return-type

为什么这样做:

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

但这并没有(a并且f交换了位置):

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

a没有在该范围内声明(在 decltype 内)但添加显式this->使其工作。

Joh*_*itb 5

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

这是有效的,因为在尾随返回类型中,周围类的成员是可见的。不是所有成员,而只是在它之前声明的成员(在尾随返回类型中,类被认为是完整的,与函数体相反)。那么这里做了什么:

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

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

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

  • a当前实例化的成员也是如此。但它是依赖的,因为用于声明它的类型 ( A) 是依赖的。但是,这在您的代码中无关紧要。无论是否依赖,a都已找到且代码有效。

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

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

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

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

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


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

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

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

类似地,如果对象表达式的类型为当前实例化的类成员访问表达式中的 id 表达式不引用当前实例化的成员或未知特化的成员,即使如果包含成员访问表达式的模板没有被实例化;无需诊断。

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

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

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