Why doesn't a derived template class have access to a base template class' identifiers?

17,462

Solution 1

That's two-phase lookup for you.

Base<T>::NO_ZEROFILL (all caps identifiers are boo, except for macros, BTW) is an identifier that depends on T.
Since, when the compiler first parses the template, there's no actual type substituted for T yet, the compiler doesn't "know" what Base<T> is. So it cannot know any identifiers you assume to be defined in it (there might be a specialization for some Ts that the compiler only sees later) and you cannot omit the base class qualification from identifiers defined in the base class.

That's why you have to write Base<T>::NO_ZEROFILL (or this->NO_ZEROFILL). That tells the compiler that NO_ZEROFILL is something in the base class, which depends on T, and that it can only verify it later, when the template is instantiated. It will therefore accept it without trying to verify the code.
That code can only be verified later, when the template is instantiated by supplying an actual parameter for T.

Solution 2

The problem you have encountered is due to name lookup rules for dependent base classes. 14.6/8 has:

When looking for the declaration of a name used in a template definition, the usual lookup rules (3.4.1, 3.4.2) are used for nondependent names. The lookup of names dependent on the template parameters is postponed until the actual template argument is known (14.6.2).

(This is not really "2-phase lookup" - see below for an explanation of that.)

The point about 14.6/8 is that as far as the compiler is concerned NO_ZEROFILL in your example is an identifier and is not dependent on the template parameter. It is therefore looked up as per the normal rules in 3.4.1 and 3.4.2.

This normal lookup doesn't search inside Base<T> and so NO_ZEROFILL is simply an undeclared identifier. 14.6.2/3 has:

In the definition of a class template or a member of a class template, if a base class of the class template depends on a template-parameter, the base class scope is not examined during unqualified name lookup either at the point of definition of the class template or member or during an instantiation of the class template or member.

When you qualify NO_ZEROFILL with Base<T>:: in essence you are changing it from being a non dependent name to a dependent one and when you do that you delay its lookup until the template is instantiated.

Side note: What is 2-phase lookup:

void bar (int);

template <typename T>
void foo (T const & t) {
  bar (t);
}


namespace NS
{
  struct A {};
  void bar (A const &);
}


int main ()
{
  NS::A a;
  foo (a);
}

The above example is compiled as follows. The compiler parses the function body of foo and see that there is a call to bar which has a dependent argument (ie. one that is dependent on the template parameter). At this point the compiler looks up bar as per 3.4.1 and this is the "phase 1 lookup". The lookup will find the function void bar (int) and that is stored with the dependent call until later.

When the template is then instantiated (as a result of the call from main), the compiler then performs an additional lookup in the scope of the argument, this is the "phase 2 lookup". This case that results in finding void NS::bar(A const &).

The compiler has two overloads for bar and it selects between them, in the above case calling void NS::bar(A const &).

Solution 3

Seems to compile ok in vs 2008. Have you tried:

public:
    Derived( bool initZero = Base<T>::NO_ZEROFILL );
Share:
17,462

Related videos on Youtube

cheshirekow
Author by

cheshirekow

Updated on August 30, 2020

Comments

  • cheshirekow
    cheshirekow almost 4 years

    Consider:

    template <typename T>
    class Base
    {
        public:
            static const bool ZEROFILL = true;
            static const bool NO_ZEROFILL = false;
    }
    
    template <typename T>
    class Derived : public Base<T>
    {
        public: 
            Derived( bool initZero = NO_ZEROFILL );    // NO_ZEROFILL is not visible
            ~Derived();
    }
    

    I am not able compile this with GCC g++ 3.4.4 (cygwin).

    Prior to converting these to class templates, they were non-generic and the derived class was able to see the base class's static members. Is this loss of visibility in a requirement of the C++ spec or is there a syntax change that I need to employ?

    I understand that each instantiation of Base<T> will have it's own static member "ZEROFILL" and "NO_ZEROFILL", that Base<float>::ZEROFILL and Base<double>::ZEROFILL are different variables, but i don't really care; the constant is there for readability of the code. I wanted to use a static constant because that is more safe in terms of name conflicts rather than a macro or global.

    • George Robinson
      George Robinson about 6 years
      Because of the two-phase name lookup (which not all compilers use by default). There are 4 solutions to this problem: 1) Use the prefix Base<T>::NO_ZEROFILL, 2) Use the prefix this->NO_ZEROFILL, 3) Add a statement using Base<T>::NO_ZEROFILL, 4) Use a global compiler switch that enables the permissive mode. The pros & cons of these solutions are described in stackoverflow.com/questions/50321788/…
  • josesuero
    josesuero almost 15 years
    aw, you beat me to it by 20 seconds. +1
  • sbi
    sbi almost 15 years
    Actually, VC doesn't do two-phase lookup. That's why it compiles there. And that's why it is a bad idea to create a template lib using VC -- you'll have a lot of stuff to fix when you need it on any other compiler.
  • sbi
    sbi almost 15 years
    @jalf: So for once, I'm the first. :^> Feel free to improve on it.
  • cheshirekow
    cheshirekow almost 15 years
    Interesting. Do inherited non-static members then require the same qualification? i.e. Base<T>::memberFunction()
  • sbi
    sbi almost 15 years
    @ceshirekow: Yes, basically everything behind a ::, where there's something depending on a template parameter before the ::, is "dependent", and this is one of the issues arising from this. (See for an explanation of why the typename is necessary sometimes, if you want to learn more about this. It has the same reason and you'll find good explanations everywhere.)
  • Richard Corden
    Richard Corden almost 15 years
    @sbi: Are you sure this is called 2-phase lookup? I always thought that term was just for argument dependent lookup. Do you have a reference in the standard for that?
  • josesuero
    josesuero almost 15 years
    the fixes are generally fairly trivial though. It's mostly a matter of inserting a lot of typename's, and fixing the occasional 2-phase lookup.
  • sbi
    sbi almost 15 years
    @Richard: Um, I think I explained that NO_ZEROFILL is indeed argument-dependent. Depending on T, any specialization of Base<T>could define NO_ZEROFILL to be anything -- or not define it at all.
  • MSalters
    MSalters almost 15 years
    No, "Argument dependent lookup" is the official name for Koenig lookup. When resolving foo(a,b), the name foo is looked up in the namespaces of typeof(a) and typeof(b). Nothing to do with templates.
  • sbi
    sbi almost 15 years
    @MSalters: Sorry for the confusion. What I meant is that NO_ZEROFILL depends on a template argument. In that, it is argument-dependent. However, argument-dependent lookup is of course an already occupied term, so I shouldn't have used it.
  • sbi
    sbi almost 15 years
    @jalf: That's true except where it isn't. Among others, I've run into very nasty inter-dependency problems that weren't discovered with VC because VC would only really parse the template when it was instantiated -- and by then all dependent entities were in scope. During the first parse in a proper two-phase lookup, this fell to pieces and it took quite a while and a liberal sprinkling of the programmer's universal cure (indirection) to untangle the mess. The code was a lot harder to understand after that, and it would probably have been designed differently had the problem been known earlier.
  • Johannes Schaub - litb
    Johannes Schaub - litb over 13 years
    +1, nice explanation :) Too bad you seem to have stopped answering on stackoverflow. I loved your elaborative and intensive discussions.
  • Johannes Schaub - litb
    Johannes Schaub - litb over 13 years
    @MSalters what @Richard was referring to was two phase lookup when unqualified dependent function names are looked up at instantiation. It's called "two phase lookup" because a part of the function name lookup is done at the definition context and another part (the part about ADL) is done when instantiating. But the term "two phase lookup" is not a standards-defined term, so people use that term differently.
  • Richard Corden
    Richard Corden over 13 years
    It's nice to be missed! I hope to get back to answering questions in the near future.
  • stefan
    stefan about 9 years
    Please properly format your code. Explanation on why code works and where the problem in the original code is, is usually a good thing too.
  • kevin
    kevin about 8 years
    There is a similar question, and I think the answer there is good. stackoverflow.com/questions/1120833/…
  • sbi
    sbi about 8 years
    @kevin That answer mostly tells how to work around the problem, only linking to some explanation. I consider that inferior, as external links might "rot". My answer attempts to explain the underlying problem. I have added the this-> syntax to my answer (even though I'm not sure it's applicable for a member function default argument). Would that help?
  • kevin
    kevin about 8 years
    @sbi Absolutely. Thanks.
  • Carousel
    Carousel almost 8 years
    @RichardCorden I'm not fully understanding your side note to illustrate the "2-phase lookup". As my understanding, the function name bar in the function body of foo is a dependent name, because its parameter is type-dependent. Shouldn't the name lookup of bar in foo also be postponed until the actual template argument is known? Actually, clang++ 3.8.1 doesn't yield any error about the name lookup even if I comment out the line void bar (int);. So I doubt the "phase 1 lookup" for bar is actually performed.
  • Richard Corden
    Richard Corden almost 8 years
    @Carousel As the name is dependent an error is not expected during phase 1 lookup. Try the following: Add a new template overload above foo(T const &), template <typename T> void bar (T);. Change the overload in namespace NS have the same signature: template <typename T> void bar (T);. Phase 1 lookup finds 2 overloads for bar, and phase 2 finds NS::bar. The compiler is unable to select a best match between the bar(T) functions. Now move the global bar(T) below foo. The code will compile successfully because phase 1 only finds bar(int).