Item 42: Understand the two meanings of typename


Item 42: Understand the two meanings of typename

Question: what is the difference between class and typename in the following template declarations?

 template<class T> class Widget;                 // uses "class" template<typename T> class Widget;              // uses "typename" 

Answer: nothing. When declaring a template type parameter, class and typename mean exactly the same thing. Some programmers prefer class all the time, because it's easier to type. Others (including me) prefer typename, because it suggests that the parameter need not be a class type. A few developers employ typename when any type is allowed and reserve class for when only user-defined types are acceptable. But from C++'s point of view, class and typename mean exactly the same thing when declaring a template parameter.

C++ doesn't always view class and typename as equivalent, however. Sometimes you must use typename. To understand when, we have to talk about two kinds of names you can refer to in a template.

Suppose we have a template for a function that takes an STL-compatible container holding objects that can be assigned to ints. Further suppose that this function simply prints the value of its second element. It's a silly function implemented in a silly way, and as I've written it below, it shouldn't even compile, but please overlook those things there's a method to my madness:

 template<typename C>                            // print 2nd element in void print2nd(const C& container)               // container; {                                               // this is not valid C++!   if (container.size() >= 2) {      C::const_iterator iter(container.begin()); // get iterator to 1st element      ++iter;                                    // move iter to 2nd element      int value = *iter;                         // copy that element to an int      std::cout << value;                        // print the int   } } 

I've highlighted the two local variables in this function, iter and value. The type of iter is C::const_iterator, a type that depends on the template parameter C. Names in a template that are dependent on a template parameter are called dependent names. When a dependent name is nested inside a class, I call it a nested dependent name. C::const_iterator is a nested dependent name. In fact, it's a nested dependent type name, i.e., a nested dependent name that refers to a type.

The other local variable in print2nd, value, has type int. int is a name that does not depend on any template parameter. Such names are known as non-dependent names, (I have no idea why they're not called independent names. If, like me, you find the term "non-dependent" an abomination, you have my sympathies, but "non-dependent" is the term for these kinds of names, so, like me, roll your eyes and resign yourself to it.)

Nested dependent names can lead to parsing difficulties. For example, suppose we made print2nd even sillier by starting it this way:

 template<typename C> void print2nd(const C& container) {   C::const_iterator * x;   ... } 

This looks like we're declaring x as a local variable that's a pointer to a C::const_iterator. But it looks that way only because we "know" that C::const_iterator is a type. But what if C::const_iterator weren't a type? What if C had a static data member that happened to be named const_iterator, and what if x happened to be the name of a global variable? In that case, the code above wouldn't declare a local variable, it would be a multiplication of C::const_iterator by x! Sure, that sounds crazy, but it's possible, and people who write C++ parsers have to worry about all possible inputs, even the crazy ones.

Until C is known, there's no way to know whether C::const_iterator is a type or isn't, and when the template print2nd is parsed, C isn't known. C++ has a rule to resolve this ambiguity: if the parser encounters a nested dependent name in a template, it assumes that the name is not a type unless you tell it otherwise. By default, nested dependent names are not types. (There is an exception to this rule that I'll get to in a moment.)

With that in mind, look again at the beginning of print2nd:

 template<typename C> void print2nd(const C& container) {   if (container.size() >= 2) {      C::const_iterator iter(container.begin());   // this name is assumed to      ...                                          // not be a type 

Now it should be clear why this isn't valid C++. The declaration of iter makes sense only if C::const_iterator is a type, but we haven't told C++ that it is, and C++ assumes that it's not. To rectify the situation, we have to tell C++ that C::const_iterator is a type. We do that by putting typename immediately in front of it:

 template<typename C>                           // this is valid C++ void print2nd(const C& container) {   if (container.size() >= 2) {     typename C::const_iterator iter(container.begin());     ...   } } 

The general rule is simple: anytime you refer to a nested dependent type name in a template, you must immediately precede it by the word typename. (Again, I'll describe an exception shortly.)

typename should be used to identify only nested dependent type names; other names shouldn't have it. For example, here's a function template that takes both a container and an iterator into that container:

 template<typename C>                   // typename allowed (as is "class") void f(const C& container,             // typename not allowed      typename C::iterator iter);       // typename required 

C is not a nested dependent type name (it's not nested inside anything dependent on a template parameter), so it must not be preceded by typename when declaring container, but C::iterator is a nested dependent type name, so it's required to be preceded by typename.

The exception to the "typename must precede nested dependent type names" rule is that typename must not precede nested dependent type names in a list of base classes or as a base class identifier in a member initialization list. For example:

 template<typename T> class Derived: public Base<T>::Nested { // base class list: typename not public:                                 // allowed   explicit Derived(int x)   : Base<T>::Nested(x)                  // base class identifier in mem   {                                     // init. list: typename not allowed     typename Base<T>::Nested temp;      // use of nested dependent type     ...                                 // name not in a base class list or   }                                     // as a base class identifier in a   ...                                   // mem. init. list: typename required }; 

Such inconsistency is irksome, but once you have a bit of experience under your belt, you'll barely notice it.

Let's look at one last typename example, because it's representative of something you're going to see in real code. Suppose we're writing a function template that takes an iterator, and we want to make a local copy, temp, of the object the iterator points to. We can do it like this:

 template<typename IterT> void workWithIterator(IterT iter) {   typename std::iterator_traits<IterT>::value_type temp(*iter);   ... } 

Don't let the std::iterator_traits<IterT>::value_type startle you. That's just a use of a standard traits class (see Item 47), the C++ way of saying "the type of thing pointed to by objects of type IterT." The statement declares a local variable (temp) of the same type as what IterT objects point to, and it initializes temp with the object that iter points to. If IterT is vector<int>::iterator, temp is of type int. If IterT is list<string>::iterator, temp is of type string. Because std::iterator_traits<IterT>::value_type is a nested dependent type name (value_type is nested inside iterator_traits<IterT>, and IterT is a template parameter), we must precede it by typename.

If you think reading std::iterator_traits<IterT>::value_type is unpleasant, imagine what it's like to type it. If you're like most programmers, the thought of typing it more than once is ghastly, so you'll want to create a typedef. For traits member names like value_type (again, see Item 47for information on traits), a common convention is for the typedef name to be the same as the traits member name, so such a local typedef is often defined like this:

 template<typename IterT> void workWithIterator(IterT iter) {   typedef typename std::iterator_traits<IterT>::value_type value_type;   value_type temp(*iter);   ... } 

Many programmers find the "typedef typename" juxtaposition initially jarring, but it's a logical fallout from the rules for referring to nested dependent type names. You'll get used to it fairly quickly. After all, you have strong motivation. How many times do you want to type typename std::iterator_traits<IterT>::value_type?

As a closing note, I should mention that enforcement of the rules surrounding typename vary from compiler to compiler. Some compilers accept code where typename is required but missing; some accept code where typename is present but not allowed; and a few (usually older ones) reject typename where it's present and required. This means that the interaction of typename and nested dependent type names can lead to some mild portability headaches.

Things to Remember

  • When declaring template parameters, class and typename are interchangeable.

  • Use typename to identify nested dependent type names, except in base class lists or as a base class identifier in a member initialization list.




Effective C++ Third Edition 55 Specific Ways to Improve Your Programs and Designs
Effective C++ Third Edition 55 Specific Ways to Improve Your Programs and Designs
ISBN: 321334876
EAN: N/A
Year: 2006
Pages: 102

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