Где и почему я должен поставить ключевые слова "template" и "typename"? - PullRequest
1011 голосов
/ 04 марта 2009

В шаблонах, где и почему я должен ставить typename и template на зависимые имена? Что именно являются зависимыми именами в любом случае? У меня есть следующий код:

template <typename T, typename Tail> // Tail will be a UnionNode too.
struct UnionNode : public Tail {
    // ...
    template<typename U> struct inUnion {
        // Q: where to add typename/template here?
        typedef Tail::inUnion<U> dummy; 
    };
    template< > struct inUnion<T> {
    };
};
template <typename T> // For the last node Tn.
struct UnionNode<T, void> {
    // ...
    template<typename U> struct inUnion {
        char fail[ -2 + (sizeof(U)%2) ]; // Cannot be instantiated for any U
    };
    template< > struct inUnion<T> {
    };
};

У меня проблема в строке typedef Tail::inUnion<U> dummy. Я вполне уверен, что inUnion является зависимым именем, и VC ++ вполне прав, подавляя его. Я также знаю, что я должен быть в состоянии добавить template где-нибудь, чтобы сообщить компилятору, что inUnion - это идентификатор шаблона. Но где именно? И следует ли тогда предполагать, что inUnion является шаблоном класса, то есть inUnion<U> называет тип, а не функцию?

Ответы [ 6 ]

1045 голосов
/ 05 марта 2009

Чтобы разобрать программу на C ++, компилятор должен знать, являются ли определенные имена типами или нет. Следующий пример демонстрирует, что:

t * f;

Как это должно быть проанализировано? Для многих языков компилятору не нужно знать значение имени для анализа и, в основном, знать, какое действие выполняет строка кода. Однако в C ++ вышеприведенное может дать совершенно разные интерпретации в зависимости от того, что означает t. Если это тип, то это будет объявление указателя f. Однако, если это не тип, это будет умножение. Таким образом, стандарт C ++ говорит в пункте (3/7):

Некоторые имена обозначают типы или шаблоны. В общем, всякий раз, когда встречается имя, необходимо определить, обозначает ли это имя одну из этих сущностей, прежде чем продолжить анализ программы, которая его содержит. Процесс, который определяет это, называется поиском имени.

Как компилятор узнает, к чему относится имя t::x, если t относится к параметру типа шаблона? x может быть статическим элементом данных int, который может быть умножен, или в равной степени может быть вложенным классом или typedef, который может уступить объявлению. Если у имени есть это свойство - его нельзя искать, пока не известны фактические аргументы шаблона, - тогда оно называется зависимым именем (оно «зависит» от параметров шаблона).

Вы можете порекомендовать просто подождать, пока пользователь создаст экземпляр шаблона:

Давайте подождем, пока пользователь создаст экземпляр шаблона, а затем выясним настоящее значение t::x * f;.

Это будет работать и фактически допускается Стандартом как возможный подход к реализации. Эти компиляторы в основном копируют текст шаблона во внутренний буфер, и только когда требуется его создание, они анализируют шаблон и, возможно, обнаруживают ошибки в определении. Но вместо того, чтобы беспокоить пользователей шаблона (бедных коллег!) Из-за ошибок, допущенных автором шаблона, другие реализации предпочитают проверять шаблоны на ранних этапах и сообщать об ошибках в определении как можно скорее, прежде чем даже произойдет создание экземпляра.

Таким образом, должен быть способ сообщить компилятору, что определенные имена являются типами, а определенные имена - нет.

Ключевое слово "typename"

Ответ: Мы решаем, как компилятор должен это проанализировать. Если t::x является зависимым именем, тогда нам нужно добавить префикс к typename, чтобы сказать компилятору, что он должен быть разобран определенным образом. Стандарт говорит в (14,6 / 2):

Имя, используемое в объявлении или определении шаблона и зависящее от параметра-шаблона: Предполагается, что не называть тип, если применимый поиск имени не находит имя типа или имя не квалифицирован по ключевому слову typename.

Есть много имен, для которых typename не является необходимым, потому что компилятор может с помощью соответствующего поиска имени в определении шаблона выяснить, как анализировать саму конструкцию - например, с T *f;, когда T - это параметр шаблона типа. Но чтобы t::x * f; было объявлением, оно должно быть записано как typename t::x *f;. Если вы опускаете ключевое слово, а имя принимается как нетиповое, но когда экземпляр находит, что оно обозначает тип, компилятор выдает обычные сообщения об ошибках. Иногда ошибка, следовательно, дается во время определения:

// t::x is taken as non-type, but as an expression the following misses an
// operator between the two names or a semicolon separating them.
t::x f;

Синтаксис допускает typename только перед полными именами - поэтому считается, что неквалифицированные имена всегда известны как относящиеся к типам, если они это делают.

Для имен, которые обозначают шаблоны, существует аналогичный признак, на что намекает вводный текст.

Ключевое слово "template"

Помните первоначальную цитату, приведенную выше, и как стандарт требует специальной обработки для шаблонов? Давайте возьмем следующий невинно выглядящий пример:

boost::function< int() > f;

Это может показаться очевидным для читателя. Не так для компилятора. Представьте себе следующее произвольное определение boost::function и f:

namespace boost { int function = 0; }
int main() { 
  int f = 0;
  boost::function< int() > f; 
}

Это действительно правильное выражение ! Он использует оператор меньше чем для сравнения boost::function с нулем (int()), а затем использует оператор больше чем для сравнения результирующего bool с f. Однако, как вы, наверное, знаете, boost::function в реальной жизни - это шаблон, поэтому компилятор знает (14.2 / 3):

После того, как поиск по имени (3.4) обнаружит, что имя является именем шаблона, если за этим именем следует знак <, <является всегда принимается за начало списка аргументов шаблона и никогда за именем, за которым следует меньше оператор. </p>

Теперь мы вернулись к той же проблеме, что и с typename. Что если мы еще не можем знать, является ли имя шаблоном при разборе кода? Нам нужно будет вставить template непосредственно перед именем шаблона, как указано 14.2/4. Это выглядит так:

t::template f<int>(); // call a function template

Имена шаблонов могут встречаться не только после ::, но и после -> или . в доступе члена класса. Вам тоже нужно вставить ключевое слово:

this->template f<int>(); // call a function template

1097 * Зависимость * Для людей, у которых на полке толстые стандартные книги и которые хотят знать, о чем именно я говорил, я немного расскажу о том, как это указано в Стандарте. В объявлениях шаблона некоторые конструкции имеют разные значения в зависимости от того, какие аргументы шаблона вы используете для создания экземпляра шаблона: выражения могут иметь разные типы или значения, переменные могут иметь разные типы или вызовы функций могут в конечном итоге вызывать разные функции. Обычно говорят, что такие конструкции зависят от параметров шаблона. Стандарт точно определяет правила в зависимости от того, является ли конструкция зависимой или нет. Он разделяет их на логически разные группы: одна ловит типы, другая ловит выражения. Выражения могут зависеть от их значения и / или типа. Итак, мы добавили типичные примеры: Зависимые типы (например, параметр шаблона типа T) Выражения, зависящие от значения (например, нетипичный параметр шаблона N) зависимые от типа выражения (например, приведение к параметру шаблона типа (T)0) Большинство правил интуитивно понятны и создаются рекурсивно: например, тип, созданный как T[N], является зависимым типом, если N является зависимым от значения выражением или T является зависимым типом. Подробности этого можно прочитать в разделе (14.6.2/1) для зависимых типов, (14.6.2.2) для зависимых от типа выражений и (14.6.2.3) для зависимых от значения выражений. Зависимые имена Стандарт немного неясно, что именно является зависимым именем . При простом чтении (вы знаете, принцип наименьшего удивления) все, что он определяет как зависимое имя , является особым случаем для имен функций ниже. Но поскольку ясно, что T::x также необходимо искать в контексте реализации, оно также должно быть зависимым именем (к счастью, с середины C ++ 14 комитет начал изучать, как исправить это запутанное определение). Чтобы избежать этой проблемы, я прибег к простой интерпретации стандартного текста. Из всех конструкций, которые обозначают зависимые типы или выражения, их подмножество представляет имена. Поэтому эти имена являются «зависимыми именами». Название может принимать разные формы - Стандарт гласит: Имя - это использование идентификатора (2.11), идентификатора оператора-функции (13.5), идентификатора функции преобразования (12.3.2) или идентификатора шаблона (14.2), который обозначает объект или метку (6.6 .4, 6.1) Идентификатор - это просто последовательность символов / цифр, а следующие два - это форма operator + и operator type. Последняя форма template-name <argument list>. Все это имена, и при стандартном использовании в Стандарте имя может также включать квалификаторы, которые говорят, в каком пространстве имен или классе следует искать имя. Зависимое от значения выражение 1 + N не является именем, но N является. Подмножество всех зависимых конструкций, которые являются именами, называется зависимое имя . Однако имена функций могут иметь разное значение в разных экземплярах шаблона, но, к сожалению, это общее правило не учитывается. Зависимые имена функций В первую очередь это не проблема данной статьи, но все же стоит упомянуть: имена функций являются исключением, которые обрабатываются отдельно. Имя функции идентификатора зависит не само по себе, а от зависимых от типа выражений аргументов, используемых в вызове. В примере f((T)0), f является зависимым именем. В стандарте это указано в (14.6.2/1). Дополнительные примечания и примеры

В достаточном количестве случаев нам нужны typename и template. Ваш код должен выглядеть следующим образом

template <typename T, typename Tail>
struct UnionNode : public Tail {
    // ...
    template<typename U> struct inUnion {
        typedef typename Tail::template inUnion<U> dummy;
    };
    // ...
};

Ключевое слово template не всегда должно появляться в последней части имени. Он может появиться посередине перед именем класса, которое используется в качестве области видимости, как в следующем примере

typename t::template iterator<int>::value_type v;

В некоторых случаях ключевые слова запрещены, как описано ниже

  • На имя зависимого базового класса вам не разрешено писать typename. Предполагается, что данное имя является именем типа класса. Это верно как для имен в списке базового класса, так и в списке инициализатора конструктора:

     template <typename T>
     struct derive_from_Has_type : /* typename */ SomeBase<T>::type 
     { };
    
  • В декларациях об использовании невозможно использовать template после последнего ::, и комитет C ++ сказал не работать над решением.

     template <typename T>
     struct derive_from_Has_type : SomeBase<T> {
        using SomeBase<T>::template type; // error
        using typename SomeBase<T>::type; // typename *is* allowed
     };
    
131 голосов
/ 11 июля 2013

C ++ 11

Проблема

Хотя правила в C ++ 03 о том, когда вам нужно typename и template в значительной степени разумны, есть один досадный недостаток в их формулировке

template<typename T>
struct A {
  typedef int result_type;

  void f() {
    // error, "this" is dependent, "template" keyword needed
    this->g<float>();

    // OK
    g<float>();

    // error, "A<T>" is dependent, "typename" keyword needed
    A<T>::result_type n1;

    // OK
    result_type n2; 
  }

  template<typename U>
  void g();
};

Как видно, нам нужно ключевое слово устранения неоднозначности, даже если компилятор может сам понять, что A::result_type может быть только int (и, следовательно, является типом), а this->g может быть только шаблоном-членом. g объявлено позже (даже если A где-то явно специализирован, это не повлияет на код в этом шаблоне, поэтому на его значение не может повлиять более поздняя специализация A!).

Текущий экземпляр

Чтобы улучшить ситуацию, в C ++ 11 язык отслеживает, когда тип ссылается на включающий шаблон. Чтобы знать это, тип должен быть сформирован с использованием определенной формы имени, которое является его собственным именем (в приведенном выше примере A, A<T>, ::A<T>). Известно, что тип, на который ссылается такое имя, является текущим экземпляром . Может быть несколько типов, которые являются текущими экземплярами, если тип, из которого сформировано имя, является классом-членом / вложенным (тогда, A::NestedClass и A являются текущими экземплярами).

Исходя из этого понятия, язык говорит, что CurrentInstantiation::Foo, Foo и CurrentInstantiationTyped->Foo (например, A *a = this; a->Foo) - все являются членами текущего экземпляра , если обнаруживается, что они являются членами класса, являющегося текущим экземпляром, или одного из его независимых классов (просто выполняя поиск по имени).

Ключевые слова typename и template теперь больше не требуются, если классификатор является членом текущего экземпляра. Здесь следует помнить, что A<T> - это по-прежнему имя, зависящее от типа (в конце концов T также зависит от типа). Но A<T>::result_type, как известно, является типом - компилятор "волшебным образом" изучит этот тип зависимых типов, чтобы выяснить это.

struct B {
  typedef int result_type;
};

template<typename T>
struct C { }; // could be specialized!

template<typename T>
struct D : B, C<T> {
  void f() {
    // OK, member of current instantiation!
    // A::result_type is not dependent: int
    D::result_type r1;

    // error, not a member of the current instantiation
    D::questionable_type r2;

    // OK for now - relying on C<T> to provide it
    // But not a member of the current instantiation
    typename D::questionable_type r3;        
  }
};

Это впечатляет, но мы можем сделать лучше? Язык даже идет дальше, и требует , чтобы реализация снова посмотрела на D::result_type при создании экземпляра D::f (даже если она нашла свое значение уже во время определения). Если теперь результат поиска отличается или приводит к неоднозначности, программа имеет неправильную форму и должна быть предоставлена ​​диагностика. Представьте, что произойдет, если мы определили C как это

template<>
struct C<int> {
  typedef bool result_type;
  typedef int questionable_type;
};

Компилятор должен перехватить ошибку при создании экземпляра D<int>::f. Таким образом, вы получаете лучшее из двух миров: «задержанный» поиск, защищающий вас, если у вас могут возникнуть проблемы с зависимыми базовыми классами, а также «немедленный» поиск, который освобождает вас от typename и template.

Неизвестные специализации

В коде D имя typename D::questionable_type не является членом текущего экземпляра. Вместо этого язык помечает его как член неизвестной специализации . В частности, это всегда имеет место, когда вы выполняете DependentTypeName::Foo или DependentTypedName->Foo, а либо зависимый тип равен , а не текущим экземпляром (в этом случае компилятор может отказаться и сказать: «мы посмотрим позже, что является Foo) или является текущим экземпляром и именем, которое не было найдено в нем или в его независимых классах, и существуют также зависимые базовые классы.

Представьте себе, что произойдет, если у нас будет функция-член h в определенном выше A шаблоне класса

void h() {
  typename A<T>::questionable_type x;
}

В C ++ 03 язык позволял отлавливать эту ошибку, потому что никогда не было правильного способа создания экземпляра A<T>::h (какой бы аргумент вы ни указали T). В C ++ 11 у языка теперь есть дополнительная проверка, чтобы дать больше оснований для компиляторов реализовать это правило. Поскольку A не имеет зависимых базовых классов, а A не объявляет члена questionable_type, имя A<T>::questionable_type не является ни членом текущей реализации , ни членом неизвестная специализация. В этом случае не должно быть способа, чтобы этот код мог корректно компилироваться во время создания экземпляра, поэтому язык запрещает имени, в котором квалификатор является текущим экземпляром, не быть ни членом неизвестной специализации, ни членом текущего экземпляра (однако это нарушение еще не требуется для диагностики).

Примеры и мелочи

Вы можете опробовать эти знания в этом ответе и посмотреть, имеют ли смысл приведенные выше определения для вас на примере из реальной жизни (они повторяются чуть менее подробно в этом ответе).

Правила C ++ 11 делают следующий допустимый код C ++ 03 неправильно сформированным (который не был задуман комитетом C ++, но, вероятно, не будет исправлен)

struct B { void f(); };
struct A : virtual B { void f(); };

template<typename T>
struct C : virtual B, T {
  void g() { this->f(); }
};

int main() { 
  C<A> c; c.g(); 
}

Этот действительный код C ++ 03 связывает this->f с A::f во время создания экземпляра, и все в порядке. C ++ 11, однако, немедленно связывает его с B::f и требует двойной проверки при создании экземпляра, проверяя, совпадает ли поиск по-прежнему. Однако при создании экземпляра C<A>::g применяется правило Доминирование , и вместо поиска будет найдено A::f.

86 голосов
/ 08 июня 2014

ПРЕДИСЛОВИЕ

Этот пост предназначен для легкой для чтения альтернативы litb .

Основная цель та же; объяснение "Когда?" и почему?" typename и template должны применяться.


Какова цель typename и template?

typename и template могут использоваться в обстоятельствах, отличных от объявления шаблона.

В C ++ существуют определенные контексты, в которых компилятору должно быть явно сказано, как обрабатывать имя, и у всех этих контекстов есть одна общая черта; они зависят как минимум от одного шаблона-параметра .

Мы ссылаемся на такие имена, где может быть неоднозначность в толковании, как; " зависимые имена ".

В этом посте будет дано объяснение связи между зависимыми именами и двумя ключевыми словами.


SNIPPET ГОВОРИТ БОЛЕЕ 1000 СЛОВ

Попытайтесь объяснить, что происходит в следующей функции-шаблоне , для себя, друга или, возможно, вашей кошки; что происходит в выражении с пометкой ( A )?

template<class T> void f_tmpl () { T::foo * x; /* <-- (A) */ }


Это может быть не так просто, как кажется, более конкретно, результат оценки ( A ) в значительной степени зависит от определения типа, переданного в качестве параметра-шаблона T.

Различные T s могут радикально изменить семантику.

struct X { typedef int       foo;       }; /* (C) --> */ f_tmpl<X> ();
struct Y { static  int const foo = 123; }; /* (D) --> */ f_tmpl<Y> ();


Два разных сценария :

  • Если мы создадим шаблон функции с типом X , как в ( C ), у нас будет объявление pointer-to int назван x , но;

  • если мы создадим шаблон с типом Y , как в ( D ), ( A ) вместо этого будет состоять из выражения, которое вычисляет произведение 123 , умноженное на некоторую уже объявленную переменную x .



ОБОСНОВАНИЕ

Стандарт C ++ заботится о нашей безопасности и благополучии, по крайней мере, в этом случае.

Чтобы предотвратить потенциальную реализацию реализации от неприятных сюрпризов, Стандарт обязывает нас разобраться в неоднозначности имя-зависимого с помощью явно с указанием намерения в любом месте, где мы хотим обрабатывать имя как имя типа или идентификатор шаблона .

Если ничего не указано, имя-зависимого будет считаться либо переменной, либо функцией.



КАК ОБРАЩАТЬСЯ ЗАВИСИМЫЕ ИМЕНА ?

Если бы это был голливудский фильм, зависимые имена были бы болезнью, которая распространяется через телесный контакт, мгновенно воздействуя на своего хозяина, чтобы запутать его. Путаница, которая может, возможно, привести к плохо сформированной персо-, эээ ... программе.

A имя-зависимого - это любое имя, которое прямо или косвенно зависит от шаблона-параметра .

template<class T> void g_tmpl () {
   SomeTrait<T>::type                   foo; // (E), ill-formed
   SomeTrait<T>::NestedTrait<int>::type bar; // (F), ill-formed
   foo.data<int> ();                         // (G), ill-formed    
}

У нас есть четыре зависимых имени в приведенном выше фрагменте:

  • E )
    • «тип» зависит от реализации SomeTrait<T>, включая T и;
  • F )
    • "NestedTrait" , который является шаблоном-идентификатором , зависит от SomeTrait<T> и;
    • «тип» в конце ( F ) зависит от NestedTrait , который зависит от SomeTrait<T>, и;
  • G )
    • "data" , который выглядит как шаблон функции-члена , косвенно является зависимым именем , поскольку тип foo зависит от экземпляра SomeTrait<T>.

Ни один из операторов ( E ), ( F ) или ( G ) недействителен, если компилятор интерпретирует зависимые имена как переменные / функции (что, как было сказано ранее, происходит, если мы явно не говорим иначе).

РЕШЕНИЕ

Чтобы g_tmpl имел правильное определение, мы должны явно указать компилятору, что мы ожидаем тип в ( E ), template-id и тип дюйм ( F ) и идентификатор шаблона дюйм ( G ).

template<class T> void g_tmpl () {
   typename SomeTrait<T>::type foo;                            // (G), legal
   typename SomeTrait<T>::template NestedTrait<int>::type bar; // (H), legal
   foo.template data<int> ();                                  // (I), legal
}

Каждый раз, когда имя обозначает тип, все имена должны быть именами типов или пространствами имен , с учетом этого довольно легко увидеть, что мы применяем typename в начале нашего полностью квалифицированного имени .

template, однако, отличается в этом отношении, так как нет никакого способа прийти к такому выводу, как; "О, это шаблон, тогда эта другая вещь также должна быть шаблоном" . Это означает, что мы применяем template непосредственно перед любым именем , которое мы хотели бы рассматривать как таковое.



Могу ли я просто вставить ключевые слова перед любым именем?

" Могу ли я просто поставить typename и template перед любым именем? Я не хочу беспокоиться о контексте, в котором они появляются ... " - Some C++ Developer

Правила Стандарта гласят, что вы можете применять ключевые слова, если имеете дело с квалифицированным именем ( K ), но если имя не квалифицировано заявка не сформирована ( L ).

namespace N {
  template<class T>
  struct X { };
}

         N::         X<int> a; // ...  legal
typename N::template X<int> b; // (K), legal
typename template    X<int> c; // (L), ill-formed

Примечание : Применение typename или template в контексте, где это не требуется, не считается хорошей практикой; только то, что ты можешь что-то сделать, не значит, что ты должен.


Кроме того, существуют контексты, в которых typename и template явно запрещены:

  • При указании оснований, которые наследует класс

    Каждое имя, записанное в base-specier-list производного класса, уже обрабатывается как type-name , явно указывающее typename является как неправильно сформированным, так и избыточным.

                       // .------- the base-specifier-list
     template<class T> // v
     struct Derived      : typename SomeTrait<T>::type /* <- ill-formed */ {
       ...
     };
    


  • Когда шаблон-идентификатора является тем, на который ссылается директива using производного класса

     struct Base {
       template<class T>
       struct type { };
     };
    
     struct Derived : Base {
       using Base::template type; // ill-formed
       using Base::type;          // legal
     };
    
20 голосов
/ 04 марта 2009
typedef typename Tail::inUnion<U> dummy;

Однако я не уверен, что вы правильно внедрили inUnion. Если я правильно понимаю, этот класс не должен создаваться, поэтому вкладка «сбой» никогда не приведет к сбою. Возможно, было бы лучше указать, находится ли тип в объединении или нет, с простым логическим значением.

template <typename T, typename TypeList> struct Contains;

template <typename T, typename Head, typename Tail>
struct Contains<T, UnionNode<Head, Tail> >
{
    enum { result = Contains<T, Tail>::result };
};

template <typename T, typename Tail>
struct Contains<T, UnionNode<T, Tail> >
{
    enum { result = true };
};

template <typename T>
struct Contains<T, void>
{
    enum { result = false };
};

PS: посмотрите на Boost :: Variant

PS2: взгляните на списков типов , особенно в книге Андрея Александреску: Современный дизайн C ++

19 голосов
/ 07 июня 2014

Этот ответ должен быть довольно коротким и приятным, чтобы ответить (часть) на названный вопрос. Если вы хотите получить более подробный ответ, объясняющий, почему вы должны поместить его туда, перейдите по ссылке здесь .


Общее правило для добавления ключевого слова typename в основном, когда вы используете параметр шаблона и хотите получить доступ к вложенному typedef или использованию псевдонима, например:

template<typename T>
struct test {
    using type = T; // no typename required
    using underlying_type = typename T::type // typename required
};

Обратите внимание, что это также относится к мета-функциям или вещам, которые также принимают общие параметры шаблона. Однако, если предоставленный параметр шаблона является явным типом, вам не нужно указывать typename, например:

template<typename T>
struct test {
    // typename required
    using type = typename std::conditional<true, const T&, T&&>::type;
    // no typename required
    using integer = std::conditional<true, int, float>::type;
};

Общие правила добавления квалификатора template в основном аналогичны, за исключением того, что они обычно включают шаблонные функции-члены (статические или иные) для структуры / класса, который сам шаблонизирован, например:

С учетом этой структуры и функции:

template<typename T>
struct test {
    template<typename U>
    void get() const {
        std::cout << "get\n";
    }
};

template<typename T>
void func(const test<T>& t) {
    t.get<int>(); // error
}

Попытка доступа к t.get<int>() изнутри функции приведет к ошибке:

main.cpp:13:11: error: expected primary-expression before 'int'
     t.get<int>();
           ^
main.cpp:13:11: error: expected ';' before 'int'

Таким образом, в этом контексте вам нужно заранее задать ключевое слово template и назвать его так:

t.template get<int>()

Таким образом компилятор будет анализировать это правильно, а не t.get < int.

2 голосов
/ 11 августа 2018

Я помещаю превосходный ответ Дж. Л. Боргеса на аналогичный дословный вопрос от cplusplus.com, так как это самое краткое объяснение, которое я читал по этому вопросу.

В шаблоне, который мы пишем, есть два вида имен, которые можно использовать - зависимые имена и независимые имена. Зависимое имя - это имя, которое зависит от параметра шаблона; независимое имя имеет одинаковое значение независимо от того, каковы параметры шаблона.

Например:

template< typename T > void foo( T& x, std::string str, int count )
{
    // these names are looked up during the second phase
    // when foo is instantiated and the type T is known
    x.size(); // dependant name (non-type)
    T::instance_count ; // dependant name (non-type)
    typename T::iterator i ; // dependant name (type)

    // during the first phase, 
    // T::instance_count is treated as a non-type (this is the default)
    // the typename keyword specifies that T::iterator is to be treated as a type.

    // these names are looked up during the first phase
    std::string::size_type s ; // non-dependant name (type)
    std::string::npos ; // non-dependant name (non-type)
    str.empty() ; // non-dependant name (non-type)
    count ; // non-dependant name (non-type)
}

То, на что ссылается зависимое имя, может быть чем-то отличным для каждого отдельного экземпляра шаблона. Как следствие, шаблоны C ++ подвергаются «двухфазному поиску имени». Когда шаблон первоначально анализируется (до того, как произойдет какое-либо создание экземпляра), компилятор ищет независимые имена. Когда происходит конкретная реализация шаблона, параметры шаблона уже известны, и компилятор ищет зависимые имена.

На первом этапе анализатору необходимо знать, является ли зависимое имя именем типа или именем нетипа. По умолчанию зависимое имя считается именем нетипового типа. Ключевое слово typename перед зависимым именем указывает, что это имя типа.


Резюме

Используйте ключевое слово typename только в объявлениях и определениях шаблонов, если у вас есть полное имя, которое относится к типу и зависит от параметра шаблона.

...