GotW #35

Home Blog Talks Books & Articles Training & Consulting

On the
blog
RSS feed November 4: Other Concurrency Sessions at PDC
November 3
: PDC'09: Tutorial & Panel
October 26: Hoare on Testing
October 23
: Deprecating export Considered for ISO C++0x

This is the original GotW problem and solution substantially as posted to Usenet. See the book More Exceptional C++ (Addison-Wesley, 2002) for the most current solution to this GotW issue. The solutions in the book have been revised and expanded since their initial appearance in GotW. The book versions also incorporate corrections, new material, and conformance to the final ANSI/ISO C++ standard.

Typename
Difficulty: 9.5 / 10

"What's in a (type) name?" Here's an exercise that demonstrates why and how to use typename, using an idiom that's common in the standard library.

Problem

Guru Question

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

    template<class T>
    struct X_base {
      typedef T instantiated_type;
    };

    template<class A, class B>
    struct X : public X_base<B> {
      bool operator()( const instantiated_type& i ) {
        return ( i != instantiated_type() );
      }
      // ... more stuff ...
    };

Solution

1. 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?"

    template<class T>
    struct X_base {
      typedef T instantiated_type;
    };

    template<class A, class B>
    struct X : public X_base<B> {
      bool operator()( const instantiated_type& i ) {
        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 that the compiler has to parse the inlined definition of X<A,B>::operator()(), dependent names (i.e., 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 only become visible 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. Bottom line, you can't figure it out because you don't know what B is yet, and whether later on there might not 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<>
    struct X_base<int> {
        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> {
        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.

Bottom line, 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 supposed to be a type name is to throw in the keyword "typename". There are two ways we could go about it here. The less elegant is to simply write typename wherever we refer to instantiated_type:

    template<class A, class B>
    struct X : public X_base<B> {
      bool operator()
        ( const typename X_base<B>::instantiated_type& i )
      {
        return
          ( i != typename X_base<B>::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<class A, class B>
    struct X : public X_base<B> {
      typedef typename X_base<B>::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?

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: "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?"

 

Code Joke

As a bonus, here's a little code joke:

    #include <iostream>
    using std::cout;
    using std::endl;

    struct Rose {};

    struct A { typedef Rose rose; };

    template<class T>
    struct B : T { typedef typename T::rose foo; };

    template<class T>
    void smell( T ) { cout << "awful" << endl; }

    void smell( Rose ) { cout << "sweet" << endl; }

    int main() {
        smell( A::rose() );
        smell( B<A>::foo() );
    }
:-)

Copyright 2009 Herb Sutter