为什么在限定从属名称前需要使用关键字"typename",而在限定独立名称前不需要使用?

17
class A
{
   static int iterator;
   class iterator
   {
      [...]
   };
   [...]
};

我想我理解为什么这里需要typename

template <class T>
void foo() {
   typename T::iterator* iter;
   [...]
}

但是我不明白为什么这里不需要使用typename

void foo() {
   A::iterator* iter;
   [...]
}

能有人解释一下吗?


编辑:

我发现编译器不会对后者产生问题的原因,在评论中得到了很好的回答:

A::iterator的情况下,我不明白编译器为什么不会将其与static int iterator混淆? - xcrypt

@xcrypt 因为它知道两个A::iterator的含义,并且可以根据使用场合选择其中一个 - Seth Carnegie


我认为编译器在限定依赖名之前需要typename的原因,在Kerrek SB的被接受的答案中得到了很好的解释。请务必阅读该答案的评论,特别是iammilind的评论:

"T::A * x;,这个表达式可能对于T::A既是类型又是值的情况都成立。如果A是一个类型,则它将导致指针声明;如果A是一个值,则它将导致乘法。因此,一个模板将对2种不同类型具有不同的含义,这是不可接受的。"


4
在这里,没有使用模板元编程来在编译时生成代码,这是与您此处所做的不同之处。如果您愿意,可以阅读更多关于模板元编程的疯狂的内容。请注意,本翻译尽可能保持原意并简化表达,但未进行解释或添加额外内容。 - Andrew Marshall
请问在C++中,为什么有时候需要在模板参数前加上typename关键字?而有时候又需要加上template关键字?这两个关键字的作用是什么? - Flexo
@xcrypt - 我不认为他在声称那个,但它不是通常意义上的元编程。元编程可以被看作是“使用Java泛型无法完成的任务” - 它利用了模板基本上是图灵完备的事实。 - Flexo
@xcrypt 了解一下模板元编程的实际含义,你就会明白为什么你所做的不是它。你没有通过使用模板在编译时进行任何计算。 - Andrew Marshall
3
也被称为“Bjarne从未想过要做的事情”:P - Andrew Marshall
显示剩余2条评论
3个回答

52

C++中的名称可以指代三种不同的实体:类型、值和模板。

struct Foo
{
    typedef int A;                   // type
    static double B;                 // value
    template <typename T> struct C;  // template
};

三个名称Foo::AFoo::BFoo::C是三个不同层级的示例。

在上面的例子中,Foo是一个完整的类型,因此编译器已经知道Foo::A等引用的是什么。但现在想象一下:

template <typename T> struct Bar
{
    T::A x;
};

现在我们遇到了麻烦:什么是 T::A?如果 T = Foo,那么 T::A = int,这是一种类型,一切都很好。但是当 T = struct { static char A; }; 时,T::A 是一个值,这是没有意义的。
因此,编译器要求告诉它 T::AT::BT::C 应该是什么。如果你什么也不说,它会被认为是一个值。如果你说 typename,它就是一个类型名,如果你说 template,它就是一个模板。
template <typename T> struct Bar
{
    typename T::A x;    // ah, good, decreed typename

    void foo()
    {
        int a = T::B;   // assumed value, OK

        T::template C<int> z;  // decreed template
        z.gobble(a * x);
    }
};

次要检查,例如T :: B是否可以转换为intax是否可以相乘,以及C<int>是否真的有一个成员函数gobble都将延迟到实际实例化模板时再进行。但是名称表示值、类型还是模板的规范对代码的语法正确性至关重要,并且必须在模板定义期间提供。


1
非常好的解释,我只有一个问题:你会说编译器必须要求这样做,还是这只是让编译器的编写者更容易的东西?我对编译器并不是很了解,但我认为编译器应该能够自己找出来吧?它可以在模板实例化(在代码的某个部分中使用时)时直接报错。 - xcrypt
@Kerrek:嗯,你不需要像MSVC那样做。:P(免责声明:我实际上不知道MSVC是否会解析它。) - Xeo
@KerrekSB 复制粘贴,如果不正确则显示错误?但是我可以看到这种方式无法检查模板在实例化之前的定义是否有意义,但应该是可能的,对吗? - xcrypt
5
@xcrypt,编译器需要考虑几个方面。例如,T::A * x;这个表达式可以同时表示T::A是类型和T::A是值的两种情况。如果A是一个类型,那么它将导致指针声明;如果A是一个值,那么它将导致乘法运算。因此,一个单独的“模板”会有不同的含义对于两种不同的类型,这是不可接受的。因此,我们需要明确地说明它是类型还是其他。 - iammilind
@xcrypt:这并不完全错误,但“编译时间”可能会非常长...尤其是在C++中! - Kerrek SB
显示剩余3条评论

2
在非模板的foo函数中,您正在执行有效的乘法操作(假设在使用之前声明了迭代器)。尝试省略星号,您将收到编译器错误。int隐藏了类。
typename关键字不能防止隐藏。只有gcc实现它来执行此操作。因此,如果您尝试使用A作为类型实例化函数模板,则会出现编译错误,因为typenake后指定的名称将在任何符合标准的编译器上引用nontype。

0
由于编译器在实例化模板之前不知道 T::iterator 是什么,因此它无法确定 iterator 是类变量、类型、函数还是其他什么。你需要使用 typename 告诉编译器它是一种类型。

在另一种情况下,编译器确实知道我们想要什么吗?我不认为编译器在实例化之前不知道它是什么会有什么危害,这不是模板背后的整个哲学吗? - xcrypt
@xcrypt 是的,因为它知道 A 是什么,所以也知道 A::iterator 是什么。至于为什么会这样工作,那是因为语言的设计者选择让它变成这样。他们不想等待,而是让程序员告诉他们它应该是什么。 - Seth Carnegie
1
@xcrypt 它不知道 T 是什么,但它确实需要知道语句/表达式中 T::iterator角色是什么。 - Flexo
A::iterator的情况下,我不明白编译器为什么不会将其与static int iterator混淆? - xcrypt
如果它确切地知道A是什么,它可以准确查找A::iterator是什么。如果它不知道A是什么,那么这将是模板代码之外的错误,并且在模板代码内部是不明确的。 - Flexo
1
@xcrypt因为它知道两个A::iterator的含义,并且可以根据使用情况选择其中之一。 - Seth Carnegie

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接