Где и зачем нужно ставить ключевые слова template и typename в C++?
Короткий ответ
Ключевые слова template и typename нужны, чтобы помочь компилятору разобрать код, когда вы обращаетесь к членам шаблонного параметра (зависимые имена). Без них компилятор не знает, является ли T::something типом, значением или шаблоном, пока не увидит конкретный тип T. Правило простое:
typename— ставим перед именем, которое обязано быть типом (например,T::iterator).template— ставим перед именем вложенного шаблона, когда мы его инстанцируем (например,T::template rebind<U>).
Почему компилятор сам не догадывается?
Представьте, что компилятор разбирает шаблон template<typename T> void foo(). Он ещё не знает, чем будет T. Когда он видит T::something, он не может заглянуть внутрь T (ведь T может быть int, классом или чем угодно). Поэтому C++ требует от программиста явно указать: "Эй, компилятор, считай, что это тип" (typename) или "это шаблон" (template).
Пример 1: typename (тип)
Ваш пример с итератором:
template<typename T>
void printFirst(const T& container) {
// Без typename компилятор думает, что T::const_iterator — это статическое поле или вложенный класс?
// Он не знает, поэтому ошибка.
typename T::const_iterator it = container.begin();
std::cout << *it << std::endl;
}
Почему работает: typename говорит: "T::const_iterator — это тип, используй его для объявления переменной". Без этого компилятор пытается разобрать строку как умножение или вызов функции, что приводит к ошибке.
Пример 2: template (шаблон)
Допустим, у нас есть шаблонный класс-аллокатор, который содержит вложенный шаблон rebind:
template<typename T>
struct Allocator {
template<typename U>
struct rebind {
using other = Allocator<U>;
};
};
template<typename T>
void example() {
// Хотим получить Allocator<int>::rebind<double>::other
// Без template компилятор подумает, что < — это оператор "меньше"
typename T::template rebind<double>::other obj;
}
Здесь typename говорит, что T::rebind<double>::other — это тип, а template — что rebind — это шаблон (а не статическое поле или метод).
Пример 3: Когда НЕ нужно
Если имя не зависит от шаблонного параметра, ключевые слова не нужны:
struct MyClass {
using type = int;
template<typename U>
static U cast(U u) { return u; }
};
template<typename T>
void ok() {
MyClass::type x = 5; // OK: MyClass не зависит от T
MyClass::template cast<double>(3.14); // Но если бы cast зависел от T, нужен был бы template
}
Практическое правило
- Если вы пишете
SomeDependentType::nameиname— это тип — ставьтеtypename. - Если вы пишете
SomeDependentType::name<args>иname— это шаблон — ставьтеtemplate. - Запоминайте через ассоциацию: typename = тип, template = шаблон.
На практике чаще всего встречается typename (для итераторов, вложенных типов). template нужен реже, но без него не обойтись в продвинутых шаблонных библиотеках.
Альтернативное объяснение: "Правило точки с запятой"
Многие разработчики запоминают это так: если после зависимого имени идёт точка с запятой или объявление переменной — скорее всего, нужен typename. Если после имени идут угловые скобки — нужен template.
Упрощённая мнемоника для новичков
- typename — пиши, когда объявляешь переменную типа, который зависит от шаблонного параметра. Например:
typename T::iterator it; - template — пиши, когда вызываешь/инстанцируешь вложенный шаблон. Например:
obj.template rebind<int>()илиT::template rebind<int>::type.
Ещё один трюк: using-объявления
Чтобы не писать typename каждый раз, можно вынести зависимый тип в using-псевдоним внутри класса или функции:
template<typename T>
void foo(const T& c) {
using ConstIter = typename T::const_iterator; // typename нужен только здесь
ConstIter it = c.begin();
// ... используем it
}
Это делает код чище и уменьшает количество typename в теле функции. Аналогично можно поступить с template, если вложенный шаблон используется многократно.
Когда это появилось?
Ключевое слово typename для этой цели ввели в C++98. До этого использовали class (например, template<class T>), но для зависимых имён приходилось писать typename. template для вложенных шаблонов появился тоже в C++98. В C++20 ситуацию немного упростили (можно опускать typename в некоторых контекстах), но на практике старые компиляторы всё ещё требуют явного указания.