Из правила «typename должно предварять вложенные зависимые имена типов» есть исключение: typename не должно предварять вложенные зависимые имена типов в списке базовых классов или в идентификаторе базового класса в списке инициализации членов. Например:
template typename T
class Derived: public BaseT::Nested { // список базовых классов:
public: //
explicit Derived(int x)
:BaseT::Nested(x) // идентификатор базового класса
{ // в списке инициализации членов:
//
typename BaseT::Nested temp; // использование вложенного
... // зависимого имени типа не как
} // идентификатора базового
... // класса в списке инициализации
}; // членов:
Такая несогласованность несколько раздражает, но по мере приобретения опыта вы перестанете ее замечать.
Рассмотрим еще один пример использования typename, потому нечто подобное можно встретить в реальном коде. Предположим, что мы пишем шаблон функции, которая принимает итератор, и хотим сделать локальную копию – temp – объекта, на который этот итератор указывает. Это можно сделать примерно так:
template typename IterT
void workWithIterator(IterT iter)
{
typename std::iterator_traitsIterT::value_type temp(*iter);
...
}
Не пугайтесь при виде выражения std::iterator_traitsIterT::value_type. Здесь просто используются стандартные классы-характеристики (traits) (см. правило 47). Так, на C++ говорят «тип того, на что указывает объект типа *IterT». В этом предложении объявлена локальная переменная (temp) того же типа, что и объекты, на которые указывает IterT, а затем она инициализирована значением, на которое указывает iter. Если IterT будет типа vectorint::iterator, то temp будет иметь тип int. Если же IterT будет типа vectorstring::iterator, то temp будет иметь тип string. Поскольку std::iterator_traitsIterT::value_type – это вложенное зависимое имя типа (value_type вложено внутрь iterator_traitsIterT, а IterT – параметр шаблона), мы должны предварить его словом typename.
Если вам неприятно даже видеть выражение std::iterator_traitsIterT::value_type, представьте, каково набирать его на клавиатуре. Если вы, как и большинство программистов, считаете, что набрать такое более одного раза немыслимо, определите псевдоним для этого типа посредством typedef. Для имен членов классов-характеристик, к каковым относится value_type, (см. в правиле 47 информацию о классах-характеристиках), принято соглашение, согласно которому имя typedef должно совпадать с именем члена. Таким образом, определение локального typedef обычно выглядит так:
template typename IterT
void workWithIterator(IterT iter)
{
typedef typename std::iterator_traitsIterT::value_type value_type;
value_type temp(*iter);
...
}
Многих программистов соседство typedef и typename поначалу раздражает, но это логическое следствие из правила обращения к вложенным зависимым именам типов. Вы скоро привыкнете. К тому же у вас есть на то веские причины. Сколько раз вы готовы напечатать std::iterator_traitsIterT::value_type?
В качестве заключительного замечания я должен упомянуть, что не все компиляторы настаивают на строгом выполнении правил, касающихся ключевого слова typename. Некоторые принимают код, в котором typename требуется, но пропущено; некоторые принимают код, где typename присутствует, но не допускается; и некоторые (обычно это касается старых компиляторов) отвергают typename даже там, где оно необходимо. Это значит, что взаимосвязи между typename и вложенными зависимыми имен типов могут стать причиной некоторых не очень серьезных ошибок при переносе программ на другую платформу.
• В объявлениях параметров шаблона ключевые слова class и typename взаимозаменяемы.
• Используйте typename для идентификации вложенных зависимых имен типов, если они не встречаются в списке базовых классов или в качестве идентификатора базового класса в списках инициализации членов.
Правило 43: Необходимо знать, как обращаться к именам в шаблонных базовых классах