Why do I need to write typename to specify the type?

When using templates, sometimes you need to write typename for types - when and why do this?

template <class T>
inline void PRINT_ELEMENTS (const T& coll, const char* optcstr="")
{
    typename T::const_iterator pos;

    std::cout << optcstr;
    for (pos=coll.begin(); pos!=coll.end(); ++pos) {
        std::cout << *pos << ' ';
    }
    std::cout << std::endl;
}
 18
Author: Abyx, 2013-07-25

1 answers

The problem is that T::const_iterator - dependent name: it depends on the template parameter T. At this point, the compiler does not know what T will be, and cannot predict whether T::const_iterator will be a type name or, for example, the name of a static field or even a template. Therefore, it does not try to guess, and assumes that it is a field.

If you tell him, he will assume that T::const_iterator is a type, and will understand that

typename T::const_iterator pos;

- variable declaration.

Why so the compiler cannot wait to find out the meaning of the expression T::const_iterator until the type T will already be known (that is, before the template is expanded with a specific type T)? And here's why: at the time of applying the template, the T type has the right to be undefined yet! And it can also depend on the template itself. So it is impossible to postpone the clarification of the meaning of the expression. Example:

template <class T>
class comparable<T>
{
    bool compare(T& other) { return this == other; }
};

class length : public comparable<length> // в этой точке для comparable
                                         // тип T ещё не известен полностью!
{
    ...

An example of the code that illustrates the "slippery" moments is shown below. It is not compiled by gcc (since there is no standard-based typename), but the more liberal MSVC 2012 compiles and executes it.

typename that's why it's necessary to exclude such surprises.

#include "stdafx.h" // нужно для MSVC
#include <iostream>
using namespace std;

template <class T>
struct A
{
    void f()
    {
        // если T::iterator - тип, это предварительное объявление функции
        // если T::iterator - число, это объявление переменной с инициализацией
        int x(T::iterator);
    }
    void g()
    {
        int x = 5;
        {
            // если T::iterator - шаблон, принимающий числовой аргумент,
            // это инстанциация шаблона в переменную x, перекрывающую x
            // за фигурными скобками
            // если T::iterator -- экземпляр класса с перегруженным оператором <,
            // это сравнение T::iterator с нулём, а затем сравнение результата
            // со значением переменной x!
            T::iterator<0> x;
            // Кто-то всё ещё сомневается, что C++ - непредсказуемый язык?
        }
    }
};

struct T1
{
    typedef int iterator;
};

struct T2
{
    static const int iterator = 5;
};

struct T3
{
    template<int C> struct iterator
    {
        iterator() { cout << "constructing template with C = " << C << endl; }
    };
};

struct T4
{
    struct Titerator
    {
        Titerator operator < (int value)
        {
            cout << "in operator < " << value << endl;
            return Titerator();
        }
        bool operator > (int value)
        {
            cout << "in operator > " <<  value << endl;
            return false;
        }
    };
    static Titerator iterator;
};

T4::Titerator T4::iterator = T4::Titerator();

int main(int argc, char* argv[])
{
    A<T1> a1; a1.f();
    A<T2> a2; a2.f();
    A<T3> a3; a3.g();
    A<T4> a4; a4.g();
    return 0;
}

The result of the work is as follows:

constructing template with C = 0
in operator < 0
in operator > 5

 28
Author: VladD, 2013-07-27 20:54:31