TITLE: typename, typedef, and template .--------------------------------------------------------------------. | Guru of the Week problems and solutions are posted regularly on | | news:comp.lang.c++.moderated. For past problems and solutions | | see the GotW archive at http://www.cntc.com. | | Is there a topic you'd like to see covered? mailto:herbs@cntc.com | `--------------------------------------------------------------------' AUTHOR: Herb Sutter (mailto:herbs@cntc.com) GotW #35: Typename (Difficulty: 9.5/10) Problem What, if anything, is wrong with the code below? template struct X_base { typedef T instantiated_type; }; template struct X : public X_base { bool operator()( const instantiated_type& i ) { return ( i != instantiated_type() ); } // ... more stuff ... }; ------------------------------------------------------------------------- Solution >What, if anything, is wrong with the code below? This example illustrate the issue of why and how to use "typename" to refer to dependent names, and may shed some light on the question: "What's in a name?" > template > struct X_base { > typedef T instantiated_type; > }; > > template > struct X : public X_base { > bool operator()( const instantiated_type& i ) { > return ( i != instantiated_type() ); > } > // ... more stuff ... > }; 1. It's Not a Problem for Client Code [See David Vandevoorde's comments at the end! -adc] Interestingly, there is no problem here for _outside_ code that uses an instantiated X, because such outside code can refer to X::instantiated_type normally without having to write "typename" anywhere. This is because, at the time X is instantiated, all the types are known, and the dependent name X_base::instantiated_type can be looked up and resolved normally. 2. Using "typename" for Dependent Names The problem, in fact, exists solely for the _internals_ of X itself. The compiler has to parse the inlined definition of X::operator()() before the point of instantiation, but dependent names (i.e., names that depend on the template parameters, such as the inherited "instantiated_type") are not visible then. If you're wondering why not, ask yourself how you would figure out what "instantiated_type" means here... you can't because you don't know what B is yet, and whether later on there might not be a specialization for X_base that makes X_base::instantiated_type something unexpected -- any type name, or even a member variable. In the unspecialized X_base template above, X_base::instantiated_type will always be T, but there's nothing preventing someone from changing that when specializing, for example: template<> struct X_base { typedef Y instantiated_type; }; Granted, the typedef's name would be a little misleading if they did that, but it's legal. Or even: template<> struct X_base { double instantiated_type; }; Now the name is less misleading, but template X cannot work with X_base as a base class because instantiated_type is a member variable, not a type name. Bottom line, the compiler won't know how to parse the definition of X::operator()() unless we tell it what instantiated_type is... at minimum, whether it's a type or something else. Here, we want it to be a type. There are two solutions. The less elegant is to simply write typename wherever we refer to instantiated_type: template struct X : public X_base { bool operator()( const typename X_base::instantiated_type& i ) { return ( i != typename X_base::instantiated_type() ); } // ... more stuff ... }; I hope you winced when you read that. As usual, typedefs make this sort of thing much more readable, and by providing another typedef the rest of the definition works as originally written: template struct X : public X_base { typedef typename X_base::instantiated_type instantiated_type; bool operator()( const instantiated_type& i ) { return ( i != instantiated_type() ); } // ... more stuff ... }; Before reading on, does anything about adding this typedef seem unusual to you? 3. The Final (Secondary and Subtle) Point I could have used simpler examples to illustrate this (several appear in the standard in section 14.6.2), but that wouldn't have pointed out the unusual thing: The whole reason the empty base X_base appears to exist is to provide the typedef. However, derived classes usually end up just typedef'ing it again anyway. Doesn't that seem redundant? It is, but only a little... after all, it's still the specialization of X_base<> that's responsible for determining what the appropriate type should be, and that type can change for different specializations. The standard library contains base classes like this: "bags-o-typedefs" that are intended to be used in just this way. Hopefully this issue of GotW will help avert some of the questions about why derived classes re-typedef those typedefs, seemingly redundantly, and show that this effect is not really a language design glitch as much as it is just another facet of the age-old question: "What's in a name?" [As a bonus, here's a little code joke: #include struct Rose {}; struct A { typedef Rose rose; }; template struct B : T { typedef typename T::rose foo; }; template void smell( T ) { cout << "awful" << endl; } void smell( Rose ) { cout << "sweet" << endl; } int main() { smell( A::rose() ); smell( B::foo() ); } :-) -hps] VANDEVOORDE: David Vandevoorde (private email) Actually, there is a serious problem for client code: it has to include this definition and a ISO conforming compiler must then issue a diagnostic on it. Here is why... The name 'instantiated_type' is a non-dependent name in the two places it is used in the definition of template X. That means, that it must be looked up at that point, but dependent base classes (including X_base) cannot not be considered during that lookup. Thus, instantiated_type is not found and you get a diagnostic. Note that this is not really related to 'typename'. However, the solution to make the above definition legal C++ code is make the name dependent. The usual way to do that is to re-typedef the name in the derived class with a dependent qualifier... and that required typename: typedef typename X_base::instantiated_type instantiated_type; You can also use a using-declaration instead.