我正在阅读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”。但是,了解它背后的实际逻辑会很高兴。有人在乎解释吗?
答案 0 :(得分:13)
每当X为或依赖于模板参数时,您需要执行typename X::Y
的简短版本。在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<
)。
答案 1 :(得分:8)
一般来说,C ++的语法(继承自C)有一个技术缺陷:解析器必须知道某个东西是否命名一个类型,否则它只是无法解决某些歧义(例如,X * Y
a乘法,或指针Y到X类型对象的声明?这一切都取决于X是否命名一个类型......! - )。 typename
“形容词”可让您在需要时完全清晰明确(正如另一个答案所提到的那样,当涉及模板参数时,这是典型的;)。
答案 2 :(得分:3)
基本上,在编写模板代码时需要typename
关键字(即,您在函数模板或类模板中)并且您指的是依赖于可能未知的模板参数的标识符是一种类型,但必须在模板代码中解释为一种类型。
在您的示例中,您在定义#1使用typename T::Type
,因为T::Type
取决于模板参数T
,否则可能是数据成员。
在定义#2中您不需要typename T
,因为T
被声明为类型作为模板定义的一部分。