Зачем вам иногда нужно писать `typename T`, а не просто` T`?

Я читал статью Wikipedia о SFINAE и обнаружил следующий пример кода:

struct Test 
{
    typedef int Type;
};

template < typename T > 
void f( typename T::Type ) {} // definition #1

template < typename T > 
void f( T ) {}                // definition #2

void foo()
{
    f< Test > ( 10 ); //call #1 

    f< int > ( 10 );  //call #2 without error thanks to SFINAE
}

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

Ответ 1

В общем случае синтаксис С++ (унаследованный от C) имеет технический дефект: парсер ДОЛЖЕН знать, что-то называет тип, или нет, иначе он просто не может решить определенные неоднозначности (например, X * Y умножение, или объявление указателя Y объектам типа X? все зависит от того, является ли X именем типа...! -). Приложение typename "прилагательное" позволяет вам сделать это совершенно ясным и явным при необходимости (что, как упоминает другой ответ, является типичным, когда задействованы параметры шаблона;).

Ответ 2

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

Например:

template <typename T>
struct Foo {
  typename T::some_type x; // T is a template parameter. `some_type` may or may not exist depending on what type T is.
};

template <typename T>
struct Foo {
  typename some_template<T>::some_type x; // `some_template` may or may not have a `some_type` member, depending on which specialization is used when it is instantiated for type `T`
};

Как указывает sbi в комментариях, причиной двусмысленности является то, что Y может быть статическим членом, перечислением или функцией. Не зная тип X, мы не можем сказать. Стандарт указывает, что компилятор должен считать, что это значение, если оно явно не помечено как тип, используя ключевое слово typename.

И похоже, что комментаторы действительно хотят, чтобы я упоминал и другой связанный случай:;)

Если зависимое имя является шаблоном члена функции, и вы вызываете его с явным аргументом шаблона (например, foo.bar<int>()), вам нужно добавить ключевое слово template перед именем функции, как в foo.template bar<int>().

Причиной этого является то, что без ключевого слова template компилятор предполагает, что bar является значением, и вы хотите вызвать на нем меньше, чем оператор (operator<).

Ответ 3

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

В вашем примере вы используете typename T::Type в определении # 1, потому что T::Type зависит от параметра шаблона T и в противном случае может быть членом данных.

Вам не нужно typename T при определении # 2, поскольку T объявляется типом как часть определения шаблона.