通常,当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
};
但是,当我继承模板时,此代码变得非法(至少在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.)
我必须做一个
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;}
};
等。好像变量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
}
}
为什么会这样?有没有其他方法可以阻止C ++隐藏父模板类的变量?
编辑:感谢大家的精彩讨论。我必须承认我没有遵循引用C ++标准段落的论据。在没有阅读实际来源的情况下,我很难遵循它。
我可以做的最好的事情是总结讨论,引用了“禅宗之谜”中的一句话:
如果实施很难 解释,这可能是一个坏主意。
答案 0 :(得分:6)
您也可以
class B : public A<T> {
int getA() {return this->a;}
};
问题是该成员位于基础中,这取决于模板参数。正常的非限定查找是在定义点执行的,而不是在实例化时执行,因此它不会搜索依赖的基础。
答案 1 :(得分: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
}
查找名称以确定它是否是类型总是在所有相关名称的模板定义点完成:它将以下解析引导到某个方向。在第二个语句中,我们将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(); }
unqualified 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(); }
这仍然必须找到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();
}
标准规定,在查找foo
名称中operator foo
期间,我们将在p->
的范围内独立查找(A<T>
范围内C<T>::c
}),以及完整表达式出现的范围(A<T>
作为非限定名称的范围),并比较这两个名称(如果找到),是否指定相同的类型。如果我们在完整表达式的范围内查找期间不忽略依赖基类foo
,我们将在两个基类中找到A<T>
,因此具有歧义。忽略p->
意味着我们在查找TypeNameSugar
时会找到一次名称,而在我们查找{{1}}时会再次找到该名称。
答案 2 :(得分:2)
这是一个常见的问题,但无论是功能,类型还是属性,都可以完全绕过它。
问题出现在模板类和函数的两阶段评估的实现上。通常,标准要求对模板进行两次评估:
在第一次评估期间,模板参数是未知的,因此不可能知道基类将是什么......并且特别是如果它包含a
成员。任何不依赖于其中一个模板参数的符号都应该明确定义并进行检查。
通过明确定义范围,您可以通过使符号依赖于模板参数来将检查延迟到第二次评估。
使用Boost
作为灵感:
template <class A1, class A2, class A3>
class MyClass: public Base<A1,A2,A3>
{
public:
typedef Base<A1,A2,A3> base_;
void foo()
{
// Accessing type
bar_type x; // ERROR: Not dependent on template parameter
typename base_::bar_type x;
// Accessing method
bar(); // ERROR: Not dependent on template parameter
this->bar();
base_::bar();
// Accessing attribute
mBar; // ERROR: Not dependent on template parameter
this->mBar;
base_::mBar;
};
}; // class MyClass
我喜欢定义Boost
内部typedef的base_
习语。首先,它肯定有助于定义构造函数,其次通过明确地定义来自Base类的内容,它为那些通过代码的人提供了明确的信息。