这个问题的灵感来自this other question。在尝试回答这个问题时,我明白自己有很多问题。所以......考虑以下几点:
struct S1
{
enum { value = 42 };
};
template <class T> struct S2
{
typedef S1 Type;
};
template <class T> struct S3
{
typedef S2<T> Type;
};
template <class T> struct S4
{
typedef typename T::Type::Type Type; //(1)//legal?
enum {value = T::Type::Type::value }; //(2)//legal?
};
int main()
{
S4<S3<S2<S2<S1> > > >::value;
}
这与MSVC9.0和Online Comeau成功编译。但是, 困扰我的是我不理解{1}在(1)中引用的内容以及为什么我们在(2)中不需要typename
。
我已经尝试过这两种语法(合成语法?),我认为它们应该在MSVC上都失败了:
typename
和
typedef typename T::typename Type::Type Type;
enum {value = typename T::typename Type::Type::value };
当然,解决方法是使用这样的连续 typedef typename (typename T::Type)::Type Type;
enum {value = (typename (typename T::Type)::Type)::value };
:
typedef
除了好的风格,我们在语法上 必须 使用我提到的解决方法吗?
其余只是一个有趣的例子。无需阅读。与问题无关。
请注意,尽管MSVC接受原始的奇怪语法而没有多个 typedef typename T::Type T1;
typedef typename T1::Type Type;
enum { value = Type::value};
s(我的意思是(1)和(2)),但它会导致奇怪的行为,如上述问题所示。我想我也会在这里以简洁的形式提出这个例子:
typename
这不编译。我提到的解决方法解决了这个问题,但我确信这里的问题是我最初的问题 - 缺少typename,但你真的不知道在哪里贴一个。 非常感谢提前。
答案 0 :(得分:7)
作用域运算符::
之前的名称必须始终是名称空间或类(或枚举)名称,并且名称空间名称不能相关。所以你不必告诉编译器这是一个类名。
我不仅仅是这样做,标准说([temp.res]
部分):
在mem-initializer-id,base-specifier或elaborated-type-specifier中用作名称的限定名称被隐式假定为命名类型,而不使用
typename
关键字。在嵌套名称说明符中,它立即包含依赖于模板参数的嵌套名称说明符,隐式假定标识符或simple-template-id为name一种类型,不使用typename
关键字。 [ 注意: 这些结构的语法不允许typename
关键字。 - 结束说明]
T::
,T::Type::
和T::Type::Type::
是嵌套名称说明符,它们不需要标有typename
。< / p>
本节显然可以,并且可以说应该包含豁免列表中的typedef声明的类型说明符。但请记住, type-specifiers 可能会变得非常复杂,尤其是在typedef声明中。现在,很可能在typedef 类型说明符中多次需要typename
关键字,因此需要进行更多分析才能说服我typename
永远不需要在typedef中。
在typedef typename T::Type::Type Type
中,T::Type::Type
需要使用typename
关键字,因为其嵌套名称说明符(T::Type::
)是一个依赖关键字名称和标准说(同一部分):
当qualified-id旨在引用不是当前实例化成员的类型(14.6.2.1)并且其嵌套名称说明符引用依赖类型时,它应该由关键字typename预先定义,形成一个typename-specifier。如果typename-specifier中的qualified-id不表示类型,则程序格式不正确。
答案 1 :(得分:3)
typename的要点是允许在实例化模板定义之前对其进行基本检查。在不知道名称是否是类型的情况下解析C ++是不可能的(a*b;
表达式语句或指针符b
的声明。)
在模板定义中,简单标识符的类别(类型或非类型)始终是已知的。但是一个合格的(依赖)名称不能 - 对于任意T,T :: x可以是。
因此,该语言允许您通过使用typename关键字告诉编译器限定名称表示类型。如果不这样做,编译器需要将其视为值类型。在任何一种情况下,误导编译器都是错误的。
即使在某些明显需要类型的情况下(例如在typedef中),该规则也适用。
只有完全限定名称才需要消除歧义 - typename A::B::C
告诉您C是一种类型;没有必要知道有关A或B的任何信息来解析出现限定名称的上下文。
在您的示例(1)中,typename表示T::Type::Type
是一种类型。在(2)中,您不能使用typename,因为T::Type::value
不是类型。两个案例都没有说明T::Type
,因为这是无关紧要的。 (虽然可以推断它必须是一种类型,因为否则你不能将::
应用于它。)
我怀疑你的MSVC问题只是该编译器中的一个错误(因为它不能正确处理两阶段查找而臭名昭着),尽管我不得不承认我不是100%肯定。
答案 2 :(得分:2)
typedef typename T::Type::Type Type; //(1)//legal?
enum {value = T::Type::Type::value }; //(2)//legal?
在(1)中你说T :: Type :: Type 是一个类型名称
在(2)中你没有提及T :: Type :: Type :: value ,默认情况下它将被解析为非类型
答案 3 :(得分:-1)
typedef typename T :: Type :: Type Type; //(1)//合法?
我自己不明白这里需要typename
。因为typedef
只能应用于typename
。也许C ++语法就是这样设计的。
enum {value = T :: Type :: Type :: value}; //(2)//合法?
你不能使用typename
因为,它应该是一个值。这是合乎逻辑的,当你写enum { value = ??? };
时,???
必须始终只是一个值。
答案 4 :(得分:-3)
typename
指的是第一个依赖类型。在您的特定情况下:
typedef typename T::type1::type2 Type;
它引用T::type1
,告诉它是一个从属名称(取决于模板参数T)。
对于常量值,您不需要typename,因为它是值 - 而不是类型。如果未定义该值,则会出现编译错误。
修改强>
struct S1
{
enum { value = 42 };
};
template <class T> struct S2
{
typedef S1 Type;
};
template <class T> struct S3
{
typedef S2<T> Type;
};
template <class T> struct S4
{
typedef typename T::Type::Type Type; //(1)//legal?
enum {value = T::Type::Type::value }; //(2)//legal?
};
让我们慢慢地通过这个例子。此S4<S3<S2<S2<S1> > > >
类型中发生的情况是:由于T为S3<S2<S2<S1> > >
,因此typename T::Type
会扩展为S2<S2<S1> >::Type
,这是一种完整类型(不依赖于模板参数任何进一步)。因此,您不需要在第一个依赖类型名后使用typename。