I l @ ve RuBoard |
This gets us back into the field of name lookup. A motivating example is dependent names in templates ”that is, given the following code: // Example 5-1 // template<typename T> void f() { T::A* pa; // what does this line do? } the name T::A is a dependent name because it depends on the template parameter T . In this case, the programmer probably expects T::A to be a nested class or typedef within T , because the code will not compile correctly if it is something else, such as a static member variable or function. The problem is that the standard says:
This brings us to the main question:
This example illustrates 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?" // Example 5-2 // template<typename T> class X_base { public: typedef T instantiated_type; }; template<typename A, typename B> class X : public X_base<B> { public: bool operator()( const instantiated_type& i ) const { return i != instantiated_type(); } // ... more stuff ... }; 1. Use typename for Dependent NamesThe problem with X is that instantiated_type is meant to refer to the typedef supposedly inherited from the base class X_base<B> . Unfortunately, at the time the compiler parses the inlined definition of X<A,B>::operator()() , dependent names (again, names that depend on the template parameters, such as the inherited X_Base:: instantiated_type ) are not visible, and so the compiler will complain that it doesn't know what instantiated_type is supposed to mean. Dependent names become visible only later, at the point where the template is actually instantiated . If you're wondering why the compiler couldn't just figure it out anyway, pretend that you're a compiler and ask yourself how you would figure out what instantiated_type means here. It's an illuminating exercise. Bottom line: You can't figure it out because you don't know what B is yet, and whether later on there might be a specialization for X_base<B> that makes X_base<B>::instantiated_type something unexpected ”any type name, or even a member variable. In the unspecialized X_base template above, X_base<T>::instantiated_type will always be T , but there's nothing preventing someone from changing that when specializing . For example: template<> class X_base<int> { public: 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<> class X_base<double> { public: double instantiated_type; }; Now the name is less misleading, but template X cannot work with X_base <double> as a base class, because instantiated_type is a member variable, not a type name. In summary, the compiler won't know how to parse the definition of X<A,B>::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. The way to tell the compiler that something like this is a type name is to throw in the keyword typename . There are two ways to go about it here. The less elegant is to simply write typename wherever we refer to instantiated_type : // Example 5-2(a): Somewhat horrid // template<typename A, typename B> class X : public X_base<B> { public: bool operator()( const typename X_base<B>::instantiated_type& i ) const { return i != typename X_base<B>::instantiated_type(); } // ... more stuff ... }; I hope you winced when you read that. As usual, typedef s make this sort of thing much more readable, and by providing another typedef , the rest of the definition works as originally written: // Example 5-2(b): Better // template<typename A, typename B> class X : public X_base<B> { public: typedef typename X_base<B>::instantiated_type instantiated_type; bool operator()( const instantiated_type& i ) const { return i != instantiated_type(); } // ... more stuff ... }; Before reading on, does anything about adding this typedef seem unusual to you? 2. The Secondary (and Subtle) PointI 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, namely "bags-o- typedef s" which are intended to be used in just this way. Hopefully, this Item will help avert some of the questions about why derived classes re- typedef those typedef s, 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?" PostscriptAs a bonus, here's a little typename - related code joke: #include <iostream> class Rose {}; class A { public: typedef Rose rose; }; template<typename T> class B : public T { public: typedef typename T::rose foo; }; template<typename T> void smell( T ) { std::cout << "awful"; } void smell( Rose ) { std::cout << "sweet"; } int main() { smell( A::rose() ); smell( B<A>::foo() ); // :-) } |
I l @ ve RuBoard |