关于c ++:官方说,什么是typename?

Officially, what is typename for?

有时,我会看到一些真正无法识别的错误消息在使用模板时由gcc发出…具体地说,我遇到了一些问题,在这些问题中,看似正确的声明导致了非常奇怪的编译错误,通过在声明的开头加前缀"typename"关键字而神奇地消失了。(例如,就在上周,我将两个迭代器声明为另一个模板类的成员,我必须这样做)……

排字的故事是什么?


以下是约瑟提斯书的引述:

The keyword typename was introduced to
specify that the identifier that
follows is a type. Consider the
following example:

1
2
3
4
5
6
template <class T>
Class MyClass
{
  typename T::SubType * ptr;
  ...
};

Here, typename is used to clarify that
SubType is a type of class T. Thus,
ptr is a pointer to the type
T::SubType. Without typename, SubType
would be considered a static member.
Thus

1
T::SubType * ptr

would be a multiplication of value
SubType of type T with ptr.


斯坦·利普曼的博客建议:

Stroustrup reused the existing class
keyword to specify a type parameter
rather than introduce a new keyword
that might of course break existing
programs. It wasn't that a new keyword
wasn't considered -- just that it
wasn't considered necessary given its
potential disruption. And up until the
ISO-C++ standard, this was the only
way to declare a type parameter.

因此,stroustrup基本上重用了类关键字,而没有引入一个新的关键字,这个关键字后来在标准中被更改,原因如下

如示例所示

1
2
3
4
5
6
7
template <class T>
class Demonstration {
public:
void method() {
    T::A *aObj; // oops …
     // …
};

语言语法错误地将T::A *aObj;解释为算术表达式,因此引入了一个新的关键字typename

1
typename T::A* a6;

它指示编译器将随后的语句视为声明。

Since the keyword was on the payroll,
heck, why not fix the confusion caused
by the original decision to reuse the
class keyword.

所以我们两个都有

你可以看一下这篇文章,它肯定会对你有所帮助,我只是尽可能地从中提取了一些信息。


考虑一下代码

1
2
3
4
5
template<class T> somefunction( T * arg )
{
    T::sometype x; // broken
    .
    .

不幸的是,编译器不需要通灵,也不知道t::someType最终是引用类型名还是t的静态成员。因此,我们使用typename来告诉它:

1
2
3
4
5
template<class T> somefunction( T * arg )
{
    typename T::sometype x; // works!
    .
    .

在某些情况下,当您引用所谓的依赖类型的成员(意思是"依赖于模板参数")时,编译器不能总是明确地推断出结果构造的语义意义,因为它不知道这是什么类型的名称(即它是类型的名称、数据成员的名称还是某个对象的名称)否则)。在这种情况下,您必须通过显式地告诉编译器该名称属于定义为该依赖类型成员的类型名来消除这种情况的歧义。

例如

1
2
3
template <class T> struct S {
  typename T::type i;
};

在本例中,代码编译所必需的关键字typename

当您想要引用依赖类型的模板成员时,也会发生同样的事情,即引用指定模板的名称。您还必须使用关键字template来帮助编译器,尽管它的位置不同

1
2
3
template <class T> struct S {
  T::template ptr<int> p;
};

在某些情况下,可能需要同时使用

1
2
3
template <class T> struct S {
  typename T::template ptr<int>::type i;
};

(如果语法正确的话)。

当然,关键字typename的另一个作用是在模板参数声明中使用。


秘密在于模板可以专门用于某些类型。这意味着它还可以为几种类型定义完全不同的接口。例如,你可以写:

1
2
3
4
5
6
7
8
9
template<typename T>
struct test {
    typedef T* ptr;
};

template<>         // complete specialization
struct test<int> { // for the case T is int
    T* ptr;
};

有人可能会问,为什么这是有用的,而且是真的:那看起来真的没用。但要记住,例如,std::vectorreference型看上去与其他T型完全不同。诚然,它不会将reference型从一种类型改变为另一种类型,但它仍然可能发生。

现在,如果您使用这个test模板编写自己的模板,会发生什么情况。像这样的东西

1
2
3
4
5
template<typename T>
void print(T& x) {
    test<T>::ptr p = &x;
    std::cout << *p << std::endl;
}

这对你来说似乎没问题,因为你认为test::ptr是一种类型。但编译器并不知道,实际上,标准甚至建议他期望相反,test::ptr不是一种类型。为了告诉编译器您需要什么,您必须在前面添加一个typename。正确的模板如下所示

1
2
3
4
5
template<typename T>
void print(T& x) {
    typename test<T>::ptr p = &x;
    std::cout << *p << std::endl;
}

底线:在模板中使用嵌套类型的模板之前,必须添加typename。(当然,只有当模板的模板参数用于该内部模板时。)


两种用途:

  • 作为模板参数关键字(而不是"class")
  • typename关键字告诉编译器标识符是类型(而不是静态成员变量)
  • 1
    2
    3
    4
    template <typename T> class X  // [1]
    {
        typename T::Y _member;  // [2]
    }


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    #include <iostream>

    class A {
    public:
        typedef int my_t;
    };

    template <class T>
    class B {
    public:
        // T::my_t *ptr; // It will produce compilation error
        typename T::my_t *ptr; // It will output 5
    };

    int main() {
        B<A> b;
        int my_int = 5;
        b.ptr = &my_int;
        std::cout << *b.ptr;
        std::cin.ignore();
        return 0;
    }