In depth understanding of the C++ keyword typename

  • 2020-04-02 01:06:14
  • OfStack

Question: what's the difference between class and typename in the template declarations below?

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

Answer: no. When you declare a template type parameter, class and typename mean exactly the same thing. Some programmers prefer to use class all the time because it's easier to type. Others (including myself) prefer typename because it implies that the parameter need not be a class type. A few developers use typename when any type is allowed, leaving the class for situations where only user-defined types are accepted. But from a C++ point of view, class and typename mean exactly the same thing when they declare a template parameter.
However, C++ does not always treat class and typename as equals. Sometimes you have to use typename. To understand this, we have to discuss the two types of names that you will refer to in a template.
Suppose we have a template for functions that get objects that can be assigned to ints held in an stl-compatible container. Further assume that the function simply prints the value of its second element. It's a muddle-headed function implemented in a muddle-headed way, and as I write below, it doesn't even compile, but put that aside -- there's a way to find my stupidity:

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 two local variables in this function, iter and value. The type of iter is C::const_iterator, a type that depends on template parameter C. Names in a template that depend on a template parameter are called dependent names. When a dependent names is nested inside a class, I call it nested dependent names. C::const_iterator is a nested dependent name. In fact, it is an nested dependent type name (nested dependent type name), that is, a nested dependent name (nested dependent name) that refers to a type (type).
The other local variable (local variable) value in print2nd has an int. Int is a name that does not depend on any template parameter. Such names are known as non-dependent names. I can't figure out why they don't call it independent names. If, like me, you find the term "non-dependent" a nuisance, you resonate with me, but "non-dependent" is the term for such names, so, like me, roll your eyes and abandon your self-assertion.)
Nested dependent names cause parsing difficulties. For example, suppose we start print2nd this way, rather foolishly:

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

This looks like we declared x as a local variable pointing to C::const_iterator. But it only seems so because we know that C::const_iterator is a type. But what if C::const_iterator isn't a type? What if C has a static data member that happens to be called const_iterator? And what if x happens to be the name of a global variable? In this case, instead of declaring a local variable, the code above becomes C::const_iterator times x! Sure, this sounds silly, but it's possible, and the person writing the C++ parser must consider all possible inputs, even stupid.
Until C is known, there is no way to know whether C::const_iterator is a type or not, and C is not known when the template print2nd is parsed. C++ has a rule to solve 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 one exception to this rule, and I'll tell you later.
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

It should be clear by now why this is not legal C++. Iter's declaration only makes sense if C::const_iterator is a type, but we don't tell C++ that it is, and C++ assumes it isn't. To turn this around, we must tell C++ C::const_iterator is a type. We put the typename in front of it to do this:

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 get involved with a nested dependent typename (nested dependent typename) in a template, you must put the word typename in front of it. (again, I'll describe an exception later.)
Typename should only be used to identify nested dependent type names (nested dependent type names). It should not be used by any other name. For example, this is a function template that gets a container and an iterator in 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 an nested dependent typename (it is not nested inside something that depends on a template parameter), so it does not have to be prefixed by typename when the container is declared, but C::iterator is a nested dependent typename (nested dependent typename), so it must be prefixed by typename.
An exception to the "typename must precede e nested dependent type names" rule ("typename must precede nested dependent type names") is that typename does not have to precede in a list of base classes (a list of base classes) or as a base classes in a member initialization list (a list of member initializers) Nested dependent type name of an identifier (base class identifier). Such as:

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
};

This contradiction is annoying, but once you've gained a little experience from the experience, you hardly notice it.
Let's look at the last example of typename, because it is representative of the actual code you see. Suppose we are writing a function template that gets an iterator, and we want to make a local copy of the object that iterator points to, temp, we can do 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 scares you. That's just a use of standard traits class, which in C++ is "the type of thing pointed to by objects of type IterT." This statement declares a local variable (local variable) (temp) of the same type as the object pointed to by IterT objects, and initializes the temp with the object pointed to by iter. If IterT is a vector < int > ::iterator, temp is of type int. If IterT is list < The string > ::iterator, temp is of type string. Because STD: : iterator_traits < IterT > ::value_type is a nested dependent type name (value_type is nested in iterator_traits) < IterT > Inside, and IterT is a template parameter, we must have it prefixed by typename.
If you want to read STD ::iterator_traits < IterT > ::value_type is annoying, just imagine the same thing as it to represent it. If you're like most programmers and you're afraid to type it multiple times, you need to create a typedef. A common convention for trait member names like value_type is that the typedef name is the same as the trait member name, so a local typedef like this is usually defined as:

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

Many programmers initially found the "typedef typename" juxtaposing jarred, but it was a reasonable by-product of rules involving nested dependent type names (nested dependent type names). You'll get used to it pretty quickly. You have a strong motive after all. You type typename STD ::iterator_traits < IterT > :: how long does value_type take?
As a conclusion, I should mention the differences between compilers and compilers in the execution of rules around typename. Some compilers accept code that is missing when typename is required; Some compilers accept code that exists when typename is not allowed; A few (usually old) will refuse to allow typename to appear where it must. This means that the interaction between typename and nested dependent type names can cause some minor portability problems.
Things to Remember
, class and typename are interchangeable when declaring template parameters.
, typename is used to identify nested dependent type names (nested dependent type names), except as a base class identifier (base class identifier) in base class lists (base class lists) or in a member initialization list (member initialization list).

Related articles: