Почему вам иногда нужно написать `typename T` вместо просто `T`?
Вопрос
Я читал статью в Википедии о СФИНАЭ и столкнулся со следующим примером кода:
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".Тем не менее, было бы неплохо знать действительную логику, стоящую за этим.Кто-нибудь потрудится объяснить?
Решение
В общем, синтаксис C ++ (унаследованный от C) имеет технический дефект:анализатор ДОЛЖЕН знать, называет ли что-то тип или нет, в противном случае он просто не может разрешить определенные двусмысленности (например, является X * Y
умножение или объявление указателя Y на объекты типа X?все зависит от того, называет ли X тип ...!-).В typename
"прилагательное" позволяет вам сделать это совершенно ясным и недвусмысленным, когда это необходимо (что, как упоминается в другом ответе, типично, когда задействованы параметры шаблона;-).
Другие советы
Короткая версия, которую вам нужно сделать 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<
) на нем.
В принципе, вам нужно typename
ключевое слово, когда вы пишете код шаблона (т.е.вы находитесь в шаблоне функции или шаблоне класса), и вы ссылаетесь на идентификатор, который зависит от параметра шаблона, который может быть неизвестен как тип, но должен интерпретироваться как тип в коде вашего шаблона.
В вашем примере вы используете typename T::Type
в определении № 1 , потому что T::Type
зависит от параметра шаблона T
и в противном случае мог бы быть элементом данных.
Тебе не нужно typename T
в определении №2 как T
объявляется как тип как часть определения шаблона.