10

这个问题的灵感来自另一个问题。在试图回答这个问题时,我明白我自己有很多问题。所以......考虑以下几点:

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 成功编译。但是,困扰我的是我不明白typename(1)中指的是什么以及为什么我们不需要typename(2)中的内容。

我已经尝试了这两种我认为应该在 MSVC 上失败的语法(语法?):

    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像这样使用连续的 s:

   typedef typename T::Type T1;
   typedef typename T1::Type Type;
   enum { value = Type::value};  

抛开好的风格,我们在语法上是否必须使用我提到的解决方法?

其余的只是一个有趣的例子。无需阅读。与问题无关。

请注意,虽然 MSVC 接受原始的奇怪语法而没有多个typenames(我的意思是 (1) 和 (2)),但它会导致上述问题中的奇怪行为。我想我也会在这里以简洁的形式展示这个例子:

struct Good
{
    enum {value = 1}; 
};
struct Bad
{
    enum {value = -1};  
};

template <class T1, class T2>
struct ArraySize
{
    typedef Bad Type;
};
template <class T>
struct ArraySize<T, T>
{
    typedef Good Type;
};

template <class T>
struct Boom
{
    char arr[ArraySize<T, Good>::Type::value]; //error, negative subscript, even without any instantiation
};

int main()
{
    Boom<Good> b; //with or without this line, compilation fails.
}

这不编译。我提到的解决方法解决了这个问题,但我确信这里的问题是我最初的问题 - 缺少类型名,但你真的不知道在哪里贴一个。首先十分感谢。

4

5 回答 5

8

范围运算符之前的名称::必须始终是命名空间或类(或枚举)名称,并且命名空间名称不能依赖。所以你不必告诉编译器这是一个类名。


我不只是编造这个,标准说(部分[temp.res]):

在 mem-initializer-id、基本说明符或详细类型说明符中用作名称的限定名称被隐式假定为命名类型,而不使用typename关键字。在立即包含依赖于模板参数的嵌套名称说明符嵌套名称说明符中,隐式假定标识符或简单模板标识来命名类型,而不使用关键字。[ 注意:这些构造的语法不允许使用该关键字。——尾注]typenametypename

T::, T::Type::, 和T::Type::Type::嵌套名称说明符,它们不需要标记typename

本节显然可以并且可以说应该将typedef 声明的类型说明符包含在豁免列表中。但请记住,类型说明符可能会变得非常复杂,尤其是在 typedef 声明中。现在很可能在 typedef类型说明符typename中多次需要关键字,所以需要更多的分析来说服我在 typedef 中从来不需要。typename

in typedef typename T::Type::Type Type,T::Type::Type需要使用typename关键字,因为它的嵌套名称说明符( T::Type::) 是一个从属名称,并且标准说(相同部分):

当qualified-id 旨在引用不是当前实例化(14.6.2.1)成员的类型并且其nested-name-specifier 引用依赖类型时,它应以关键字typename 为前缀,形成一个类型名说明符。如果 typename-specifier 中的qualified-id 不表示类型,则程序格式错误。

于 2011-07-10T22:08:05.363 回答
3

typename 的重点是允许在实例化模板定义之前对其进行基本检查。如果不知道名称是否是类型(是a*b;表达式语句还是指针的声明b),则无法解析 C++。

在模板定义中,简单标识符的类别(类型或非类型)始终是已知的。但是一个合格的(从属)名称不能是 - 对于任意 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% 确定的。

于 2011-07-10T21:53:08.273 回答
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只字未提,默认情况下它将被解析为非类型

于 2011-07-10T18:36:41.980 回答
-1

typedef typename T::Type::Type 类型;//(1)//合法吗?

我自己不明白这里的需要typename。因为typedef只能应用于 a typename。也许 C++ 语法就是这样设计的。

枚举 {value = T::Type::Type::value }; //(2)//合法吗?

不能使用typename,因为它应该是一个值。隐含的逻辑是,当您编写enum { value = ??? };, then???必须始终仅是一个值。

于 2011-07-10T17:52:03.387 回答
-4

Thetypename指的是第一个依赖类型。在您的特定情况下:

typedef typename T::type1::type2 Type;

它指的是T::type1,告诉它是一个依赖名称(取决于模板参数 T)。

对于常量值,您不需要类型名,因为它是一个值 - 而不是类型。如果未定义该值,您将收到编译错误。

编辑

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,这是一个完整的类型(不再以任何方式依赖于模板参数)。出于这个原因,您不需要在第一个依赖类型名之后使用类型名。

于 2011-07-10T18:02:52.390 回答