C++ templates hides parent members
Usually, when A
is inheriting from B
, all the members of A
are automatically visible to B
's functions, for example
class A {
protected:
int a;
};
class B : public A {
int getA() {return a;}
//no need to use A::a, it is automatically visible
};
However when I'm inheriting with templates, this code becomes illegal (at least in 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.)
I must do one of
class B : public A<T> {
using B::a;
int getA() {return a;}
};
class B : public A<T> {
us开发者_JAVA百科ing A<T>::a;
int getA() {return a;}
};
class B : public A<T> {
int getA() {return B::a;}
};
etc. As if the variable a
is hidden by another variable of B
, in the following case:
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
}
}
Why is it so? Are there any other ways to prevent C++ from hiding the parent template class' variables?
Edit: thanks for everyone for the fascinating discussion. I must admit I didn't follow the argument which quoted paragraphs from the C++ standard. It's hard for me to follow it without reading the actual source.
The best thing I can do to summarize the discussion, is quoting a short line from "The Zen of Python":
If the implementation is hard to explain, it's (probably) a bad idea.
You can also do
class B : public A<T> {
int getA() {return this->a;}
};
The problem is that the member is in a base, which depends on a template parameter. Normal unqualified lookup is performed at the point of definition, not at the point of instantiation, and therefore it doesn't search dependent bases.
Since there are questions about how unqualified names may be dependent, or how unqualified name lookup may apply to dependent names:
Trail parse: Determining how we parse a statement
If in a template a dependent name is encountered, it is always assumed not to name a type, unless the name lookup that is applicable finds that it is a type or we prepend the name with 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
}
This lookup of a name to determine whether it is a type is always done at the point of the template definition for all dependent names: It guides the following parse into a certain direction. In the second statement, we will parse T *f1
as a declaration of a pointer, but not as a multiplication. In the last statement, we assumed during the pre-parse disambiguation that T::name
is not a type and try to parse it as an expression. This will fail, because we will expect a semicolon or some operator after T::name
. This lookup whether or not the name is a type has no influence on the meaning of the name in later phases: It won't yet bind the name to any declaration.
Actual parse
After we determined what names are types and what not, we will actually parse the template. Names that are not dependent - that is, those that are not looked up in a scope that is dependent or that are not explicitly made dependent by other rules - are looked up at the point where they are used in the template, and their meaning is not influenced by any declaration visible when instantiating.
Names that are dependent are looked up when instantiating, both in the template definition where they are used, and where their template is instantiated. This is true also for unqualified names that are dependent:
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(); }
The unqualified foo
is made dependent by the Standard because the argument T()
is type-dependent. When instantiating, we will look for functions called foo
using unqualified name lookup around the template definition, and using argument dependent lookup (meaning, roughly, in the namespace of T
) around both the template definition and the point where we instantiate it (after main
). Argument dependent lookup will then find foo
.
If Bar
now has a dependent base class, unqualified lookup must ignore that dependent base class:
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(); }
This must still find A::foo
, despite the fact that unqualified name-lookup would find a class member function if it were done (ADL will not find additional functions if a class member function was found). But unqualified namelookup will not find that function, because it's a member of a dependent base class, and those are ignored during unqualified name lookup. Another interesting case:
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();
}
The Standard states that during lookup of foo
in the operator foo
name, we will lookup independently in both the scope of p->
(which is in the scope of class A<T>
), and the scope in which the complete expression appears (which is the scope of C<T>::c
as an unqualified name), and compare the two names, if found, whether they designate the same type. If we would not ignore the dependent base class A<T>
during the lookup in the scope of the complete expression, we will find foo
in two base classes, thus having an ambiguity. Ignoring A<T>
will mean we find the name once when we do lookup into p->
, and another time when we do lookup into TypeNameSugar
.
It's a common issue, it is however perfectly possible to circumvent it, be it for functions, types or attributes.
The problem comes up with the implementation of the 2 phases evaluation of template classes and functions. Normally the standard requires that the templates be evaluated two times:
- A first time when encountered, to validate that it's correctly formed
- The second when instanciated, to actually produce the code for the types given
During the first evaluation, the template parameters are unknown, so it's impossible to tell what the base class is going to be... and notably if it contains a a
member. Any symbol that does not depend on one of the template parameters is expected to be clearly defined and will be checked.
By explicitly defining the scope you will delay the check to the second evaluation by making the symbol dependent on template parameters.
Using Boost
as inspiration:
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
I like the Boost
idiom of defining a base_
inner typedef. First it certainly helps to define the constructors and second by explicitly qualifiying what comes from the Base class it makes things clear for those treading through the code.
精彩评论