Solution

I l @ ve RuBoard

graphics/bulb.gif

1. What is typename , and what does it do?

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:

A name used in a template declaration or definition and that is dependent on a template-parameter is assumed not to name a type unless the applicable name lookup finds a type name or the name is qualified by the keyword typename .

This brings us to the main question:

2. What, if anything, is wrong with the code below?

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 Names

The 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) 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, 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?"

Postscript

As 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


More Exceptional C++
More Exceptional C++: 40 New Engineering Puzzles, Programming Problems, and Solutions
ISBN: 020170434X
EAN: 2147483647
Year: 2001
Pages: 118
Authors: Herb Sutter

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net