C++模板隐藏父成员

Ela*_*ich 5 c++ generics templates

通常,当A继承时,例如B,所有成员都会A自动对B函数可见

class A {
protected:
    int a;
};

class B : public A {
    int getA() {return a;}
    //no need to use A::a, it is automatically visible
};
Run Code Online (Sandbox Code Playgroud)

但是,当我继承模板时,此代码变得非法(至少在gcc)

template<typename T>
class A {
protected:
    int a;
};

template<typename T>
class B : public A<T> {
    int getA() {return a;}
};

templt.cpp: In member function `int B<T>::getA()':
templt.cpp:9: error: `a' undeclared (first use this function)
templt.cpp:9: error: (Each undeclared identifier is reported only once for each
function it appears in.)
Run Code Online (Sandbox Code Playgroud)

我必须做一个

class B : public A<T> {
    using B::a;
    int getA() {return a;}
};

class B : public A<T> {
    using A<T>::a;
    int getA() {return a;}
};

class B : public A<T> {
    int getA() {return B::a;}
};
Run Code Online (Sandbox Code Playgroud)

等等,如果变量a被另一个变量隐藏B,在以下情况中:

class HiddenByOverload {void hidden(){}}
class HidesByOverload : public HiddenByOverload {
    void hidden(int i) {} //different signature, now `hidden` is hidden
    void usehidden() {
        HiddenByOverload::hidden(); // I must expose it explicitly
    }
}
Run Code Online (Sandbox Code Playgroud)

为什么会这样?有没有其他方法可以阻止C++隐藏父模板类的变量?

编辑:感谢大家的精彩讨论.我必须承认我没有遵循引用C++标准段落的论据.在没有阅读实际来源的情况下,我很难遵循它.

我可以做的最好的事情是总结讨论,引用了"Python的禅宗"中的一句话:

如果实施难以解释,那可能是一个坏主意.

ava*_*kar 6

你也可以

class B : public A<T> {
    int getA() {return this->a;}
};
Run Code Online (Sandbox Code Playgroud)

问题是该成员位于基础中,这取决于模板参数.正常的非限定查找是在定义点执行的,而不是在实例化时执行,因此它不搜索依赖的基数.


Joh*_*itb 6

由于存在关于非限定名称如何依赖的问题,或者如何将非限定名称查找应用于从属名称:


跟踪解析:确定我们如何解析语句

如果在模板中遇到从属名称,则始终假定命名类型,除非适用的名称查找发现它是一个类型,或者我们在名称前加上typename:

template<typename T>
void f() {
  T f0; // T is a template type parameter => type
  T *f1;

  typename T::name g1; // "name" is assumed to be a type. 
  T::name g0; // "name" cannot be looked up here => non-type
}
Run Code Online (Sandbox Code Playgroud)

查找名称以确定它是否是类型总是在所有相关名称的模板定义点完成:它将以下解析引导到某个方向.在第二个语句中,我们将解析T *f1为指针的声明,但作为乘法.在最后一个语句中,我们假设在预解析消歧期间T::name不是类型,并尝试将其解析为表达式.这将失败,因为我们将期望分号或一些运算符T::name.无论名称是否为类型,此查找都不会影响后续阶段中名称的含义:它还不会将名称绑定到任何声明.


实际解析

在我们确定了什么名称是什么类型和什么不是,我们将实际解析模板.不依赖的名称 - 也就是那些未在相关范围中查找或未明确依赖于其他规则的名称 - 在模板中使用它们的位置进行查找,其含义为不受实例化时可见的任何声明的影响.

名称即实例,都在使用它们的模板定义时依赖的抬头,并在他们的模板实例.对于依赖的非限定名称也是如此:

template<typename T>
struct Bar {
  void bar() { foo(T()); }
};

namespace A {
  struct Baz { };
  void foo(Baz); // found!
}

int main() { Bar<A::Baz> b; b.bar(); }
Run Code Online (Sandbox Code Playgroud)

不合格 foo是由相关的标准,因为参数T()的类型有关.在实例化时,我们将foo查找在模板定义周围使用非限定名称查找调用的函数,并使用参数依赖查找(大致在命名空间中T),围绕模板定义和我们实例化它的点(之后main).然后将找到依赖于参数的查找foo.

如果Bar现在具有依赖基类,则非限定查找必须忽略该依赖基类:

template<typename T>
struct HasFoo { };

template<typename T>
struct Bar : HasFoo<T> {
  void bar() { foo(T()); }
};

namespace A {
  struct Baz { };
  void foo(Baz); // found!
}

template<>
struct HasFoo<A::Baz> {
  void foo();
};

int main() { Bar<A::Baz> b; b.bar(); }
Run Code Online (Sandbox Code Playgroud)

这仍然必须找到A::foo,尽管事实上,如果完成了非限定名称查找将找到类成员函数(如果找到类成员函数,ADL将找不到其他函数).但是不合格的namelookup将找不到该函数,因为它是依赖基类的成员,并且在非限定名称查找期间会被忽略.另一个有趣的案例

template<typename>
struct A {
  typedef int foo;
  operator int() {
    return 0;
  }
};

// makes sure applicable name-lookup
// classifies "foo" as a type (so parsing will work).
struct TypeNameSugar {
  typedef int foo;
};

template<typename T>
struct C : A<T>, TypeNameSugar {
  void c() {
    A<T> *p = this;
    int i = p->operator foo();
  }
};

int main() {
  C<void>().c();
}
Run Code Online (Sandbox Code Playgroud)

标准规定的查找期间foooperator foo名字中,我们将独立地在两个查找的范围p->(其是在类的范围A<T>),并且其中显示的完整表达的范围(这是范围C<T>::c为不合格的名称) ,并比较两个名称,如果找到,是否指定相同的类型.如果在完整表达式的范围内查找期间我们不会忽略依赖基类A<T>,我们将foo在两个基类中找到它们,因此具有歧义.忽略A<T>将意味着我们在查找时会找到一次名称,而在我们查找时会发现p->另一次TypeNameSugar.