C++ templates hides parent members

前端 未结 3 802
野性不改
野性不改 2021-02-10 03:55

Usually, when A is inheriting from B, all the members of A are automatically visible to B\'s functions, for example



        
相关标签:
3条回答
  • 2021-02-10 04:12

    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.

    0 讨论(0)
  • 2021-02-10 04:18

    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.

    0 讨论(0)
  • 2021-02-10 04:21

    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.

    0 讨论(0)
提交回复
热议问题