我想了解当使用和/或省略templatethis关键字的4种组合时,访问修饰符关于继承的4种不同行为。以下所有代码都是在g++ 4.8中完成的:

这是GrandChild类,private ly继承自Parentprivate ly继承自GrandParentpublic拥有enum n GrandParent::n。非对象客户端代码可以访问public,因为后者是enum GrandParent::n。但是从GrandChild内部无法访问GrandParent::n:

#include <iostream>
using namespace std;

struct GrandParent { enum {n = 0}; };

struct Parent : private GrandParent { enum {n = 1}; };

struct GrandChild : private Parent {
    enum {n = 2};
    void f() {cout << GrandParent::n << endl;}
    // ^ error: 'struct GrandParent GrandParent::GrandParent'
    // is inaccessible
};
int main() {
    cout << GrandParent::n << endl;
    // ^ non-object access would have outputted `0` had `GrandChild`'s
    // definition compiled or been commented out.
}

1.)是不是GrandChild拥有GrandChild基本子对象,从而导致GrandParent内部无法访问GrandParent::num,而该子对象隐藏了对private的非对象访问权限,并且其2代n的性使得基本子对象的GrandParent也无法访问?我希望该错误消息与之有关。

2)但显然不是。为什么错误会提示this->的构造函数?

3.)在GrandParent::n的定义中的f()之前添加this->将添加我在#1中预期的错误,但不会删除ctor投诉。为什么?我假设包含n是多余的,并且忽略它会导致查找尝试在ont-erangely较近范围的非对象GrandParent之前在GrandChild的范围内查找n子对象的this->

4.)为什么要编译此模板变体?似乎在功能上与非模板相似:
#include <iostream>
using namespace std;

template <unsigned int N>
struct bar : private bar<N - 1> {
    enum {num = N};
    void g() {
        static_assert(N >= 2, "range error");
        cout << bar<N - 2>::num << endl;
    }
};

template <>
struct bar<0> { enum {num = 0}; };

int main() {
    bar<2> b2;
    b2.g(); // Output: 0
}

5.)在bar<N - 2>::num的定义中在g()之前添加ojit_code会导致仅在#1中出现的编译器错误。但是为什么它不包含#2的错误?为什么它的遗漏不会产生#2的错误?

最佳答案

这里的整个问题是名称查找(我认为也是in one of your previous questions的情况)。我将尝试说明我对正在发生的事情的理解:

每个(命名的)类都有一个注入(inject)的类名。例如:

struct GrandParent
{
    // using GrandParent = ::GrandParent;
    enum {n = 0};
};

您可以使用这个注入(inject)的类名来引用类本身。对于普通类(非限定查找无论如何都可以在周围的作用域中找到名称GrandParent)来说,它不是很有用,但对于派生类和类模板而言,它不是:
namespace A
{
    struct Foo
    {
        // using Foo = ::A::Foo;
    };
};

struct Bar : A::Foo
{
    void woof(Foo); // using the injected-class-name `::A::Foo::Foo`
};

template<class T, int N, bool b>
struct my_template
{
    // using my_template = ::my_template<T, N, b>;
    void meow(my_template); // using the injected-class-name
};

这不是“它是基类子对象的一部分”中的继承,而是指定了不限定查找的方式:如果在当前类的作用域中找不到该名称,则将搜索基类作用域。

现在,对于OP中的第一个(非模板)示例:
struct Parent : private GrandParent
{
    // using Parent = ::Parent;

    enum {n = 1}; // hides GrandParent::n
};

struct GrandChild : private Parent {
    // using GrandChild = ::GrandChild;

    enum {n = 2};
    void f() {cout << GrandParent::n << endl;}
    // ^ error: 'struct GrandParent GrandParent::GrandParent'
    // is inaccessible
};

在这里,表达式GrandParent::n调用名称GrandParent的非限定名称查找。当找到名称(并且不考虑周围的作用域)时,不合格的查找停止时,它将找到注入(inject)的类名称GrandParent::GrandParent。也就是说,查找先搜索GrandChild的范围(找不到名称),然后搜索Parent的范围(找不到名称),最后搜索GrandParent的范围(在其中找到注入(inject)的类名称)。这是在访问检查之前完成的,并且与访问检查无关。

找到名称GrandParent后,将最终检查可访问性。从ParentGrandParent进行名称查找所需的名称查找。除了Parent的成员和 friend 之外,该路径被禁止,因为继承是私有(private)的。 (您可以看到该路径,但不能使用它;可见性和可访问性是正交的概念。)

这是标准的[basic.lookup.unqual]/8:



基类中的名称查找相当复杂,因为可能必须考虑多个基类。对于单一继承并在成员函数体的范围内查找成员的情况,它从该函数所属的类开始,然后向上遍历基类(base,base的base,base的base, ..)。参见[class.member.lookup]

模板大小写不同,因为bar是类模板的名称:
template <unsigned int N>
struct bar : private bar<N - 1> {
    enum {num = N};
    void g() {
        static_assert(N >= 2, "range error");
        cout << bar<N - 2>::num << endl;
    }
};

在这里,使用bar<N - 2>。它是一个从属名称,因为N是模板参数。因此,名称查找被推迟到g的实例化点。即使在函数之后声明了特殊化bar<0>,也可以找到它。
bar的注入(inject)的类名可以用作模板名(指类模板)或类型名(指当前实例)[temp.local]/1:



也就是说,bar<N - 2>会找到bar作为当前类(实例化)的注入(inject)类名称。当它与template-argument-list一起使用时,它指的是bar的另一种不相关的特化。基类的注入(inject)类名称被隐藏。
bar<0>::num的访问不是通过私有(private)继承的访问路径进行的,而是通过当前类的注入(inject)的类名直接进行的,引用类模板本身。可以访问num作为bar<0>的公共(public)成员。

09-05 09:03