Для чего используется ключевое слово typename в C++?

«Для чего используется ключевое слово typename в C++?» — вопрос из категории Шаблоны, который задают на 25% собеседований C/C++ Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Ключевое слово typename в C++ имеет две основные роли, и обе связаны с шаблонами.

1. Объявление параметра шаблона типа (синоним class)

В этом контексте typename и class взаимозаменяемы.

template <typename T> // Эквивалентно template <class T>
T max(T a, T b) { return (a > b) ? a : b; }

2. Указание, что зависимое имя является ТИПОМ (главное назначение)

Это критически важное использование. Внутри шаблонного кода компилятор по умолчанию не знает, является ли имя, зависящее от параметра шаблона (T::something), типом или чем-то иным (например, статической переменной). typename устраняет эту неоднозначность.

Пример проблемы и решения:

template <typename T>
class Container {
    T::iterator* ptr1; // ОШИБКА КОМПИЛЯЦИИ! Компилятор думает:
                       // 'iterator' — это статическое поле класса T?
                       // Тогда это выражение — умножение (T::iterator) * (ptr1)?

    typename T::iterator* ptr2; // КОРРЕКТНО. Явно говорим, что T::iterator — это тип,
                                // значит, это объявление указателя.
};

Типичные случаи, где требуется typename:

  • Объявление переменных зависимого типа:

    template <typename Iter>
    void process(Iter begin, Iter end) {
        typename std::iterator_traits<Iter>::value_type temp = *begin;
        // Без 'typename' компилятор не поймет, что 'value_type' — это тип.
    }
  • Указание возвращаемого типа, который является зависимым:

    template <typename C>
    typename C::value_type getFirst(const C& container) {
        if (!container.empty()) return *container.begin();
        return typename C::value_type{}; // Также требуется здесь.
    }
  • В списках инициализации базовых классов и членов (C++17 std::void_t, метапрограммирование).

Правило: Ключевое слово typename должно предшествовать полностью квалифицированному имени, которое зависит от параметра шаблона и является типом. Для независимых имен или известных типов (например, int, std::string) оно не нужно.