views:

1169

answers:

4

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 can't 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 classes static members. Is this loss of visibility a requirement of the C++ spec or is there a syntax change that I need to employ?

I understand that each instanciation 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 than a macro or global.

+2  A: 

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

public:
    Derived( bool initZero = Base<T>::NO_ZEROFILL );
Alan
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
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.
jalf
@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.
sbi
+12  A: 

That's two-phase lookup for you.

NO_ZEROFILL (all caps identifiers are boo, except for macros, BTW) is an identifier that depends on T. Since, when the compier first sees the template, there's no type for T yet, it doesn't know what Base<T> is. (There might be a specialization for some Ts that the compiler only sees later.)

Therefor you have to write Base<T>::NO_ZEROFILL. That tells the compiler that NO_ZEROFILL is something that depends on T and that it can only verify it later, when the template is instantiated.

sbi
aw, you beat me to it by 20 seconds. +1
jalf
@jalf: So for once, I'm the first. `:^>` Feel free to improve on it.
sbi
Interesting. Do inherited non-static members then require the same qualification? i.e. Base<T>::memberFunction()
cheshirekow
@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.)
sbi
@sbi thanks a ton. I keep expecting C++ templates to be as easy as Java generics... I like the concept but nothing every works the way I would expect it to :/. I hope the next revision of the standard addresses generic programming heavily.
cheshirekow
@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?
Richard Corden
@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.
sbi
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.
MSalters
@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
+4  A: 

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 &).

Richard Corden
A: 

Just an IMHO comment: This issue is a big pain in generic programming, and I don't really see any reason why the obvious solution wouldn't work: members of the super template class should be treated as members of the subclass -- just like the non-template case. If I have to mechanically prepend 'SuperClass::' in front of every member name from the superclass, why can't the compiler do this for me?

dmg
-1: For the reasons outlined in [the accepted answer](http://stackoverflow.com/questions/1239908/why-doesnt-a-derived-template-class-have-access-to-a-base-template-class/1239940#1239940).
Troubadour