C ++ templates hide parent elements

Usually, when A inherits from B , all members of A automatically visible to functions of B , 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 inherit the 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 have to do one of

 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;} }; 

etc .. As if variable A is hidden by another variable 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 that? Are there any other ways that C ++ can hide the variables of the parent class template?

Edit: Thanks everyone for the fascinating discussion. I must admit, I did not follow the arguments that the paragraphs from the C ++ standard quoted. It’s hard for me to follow him without reading the actual source.

The best I can do to summarize the discussion is a short line from "The Zen of Python":

If the implementation is complicated explain, this is (possibly) a bad idea.

+4
source share
3 answers

You can also do

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

The problem is that the element is in the database, which depends on the template parameter. The usual unskilled search is performed at the definition point, and not at the time of instantiation, and therefore it does not search for dependent databases.

+6
source

Since there are questions about how unqualified names may depend or how an unqualified name search can be applied to dependent names:


Route analysis: determining how we analyze the operator

If a dependent name is found in a template, it is always assumed that it should not indicate a type, unless a search by name that is applicable detects that it is a type, or we add the name using 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 search for a name to determine whether it is a type is always performed at the template definition point for all dependent names: it directs the next analysis in a specific direction. In the second expression, we will analyze T *f1 as a pointer declaration, but not as a multiplication. In the last statement, we assumed that in the case of a preparametric value, that T::name not a type and is trying to parse it as an expression. This will fail because we expect a semicolon or some operator after T::name . This search, regardless of whether it is a type, does not affect the meaning of the name in subsequent steps: it will not yet associate the name with any declaration.


Actual analysis

After we determine which names are types and what are not, we will actually analyze the pattern. Names that are not dependent, that is, those that are not visible in an area that depends or are not explicitly dependent on other rules, are looked at where they are used in the template, and their meaning is not influenced by any declaration visible when creating an instance.

Names that depend are looked up when creating an instance, both in the definition of the template, where they are used, and when creating their template. This is also true for unqualified names that depend on:

 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 dependent on the standard because the argument of T() is type dependent. When creating an instance, we will look for functions called foo , using the search for an unqualified name around the template definition and using a search argument dependent (which roughly means in the T namespace) around the template definition and the point at which we instantiate it (after main ). Then an argument-dependent search will find foo .

If Bar now has a dependent base class, an unskilled search should ignore this 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 should find A::foo , although an unqualified name lookup will find a member function of the class if it was done (ADL will not find additional functions if a member function of the class was found). But an unqualified namelookup will not find this function, because it is a member of a dependent base class, and those are ignored during an 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 when searching for foo in the name operator foo we will search independently both in the region p-> (which is in the region of class A<T> ) and in the region in which the full expression appears (which is the region of C<T>::c as an unqualified name) and compares two names, if found, whether they denote the same type. If we do not ignore the dependent base class A<T> during the search in the scope of the full expression, we will find foo in the two base classes, which will be ambiguous. Ignoring A<T> will mean that we will find the name once when we search in p-> , and at another time when we search in TypeNameSugar .

+6
source

This is a common problem, but it is possible to get around it, be it functions, types, or attributes.

The problem is related to the implementation of a two-stage evaluation of the classes and functions of templates. Typically, a standard requires templates to be evaluated twice:

  • The first time they met to make sure that it is properly formed
  • Second, when instantiated to actually create code for the specified types

During the first assessment, the template parameters are unknown, so it is impossible to determine what the base class will be ... and, especially, if it contains the member a . It is expected that any character that does not depend on one of the template parameters will be clearly defined and verified.

By explicitly defining the scope, you delay the check to the second assessment, making the character dependent on the 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 an internal tpedef base_ . Firstly, it certainly helps to define constructors, and secondly, by explicitly qualifying what happens to the base class, which makes things clear to those who pass the code.

+2
source

Source: https://habr.com/ru/post/1303385/


All Articles