Зачем вам иногда нужно писать `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
объявляется типом как часть определения шаблона.