正式来说,typename是做什么用的?


131

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

这是什么故事typename


Answers:


207

以下是Josuttis书中的引文:

typename引入关键字是为了指定后面的标识符是一种类型。考虑以下示例:

template <class T>
Class MyClass
{
  typename T::SubType * ptr;
  ...
};

在这里,typename用于说明 SubType是的类型class T。因此, ptr是指向type的指针 T::SubType。没有typenameSubType 将被视为静态成员。从而

T::SubType * ptr

将是SubTypetype 的值T与的乘积 ptr


2
很棒的书。请通读一次,如果愿意,可以保留它作为参考。
deft_code

1
精明的读者会意识到,成员声明的语法不允许使用乘法表达式。因此,C ++ 20 无需这样做typename(尽管并非全部!)。
戴维斯·鲱鱼

没有说服我。实例化模板后,就可以很好地定义什么是T :: Subtype
kovarex

36

斯坦·李普曼(Stan Lippman)的博客文章建议:-

Stroustrup 重用了现有的class关键字来指定类型参数,而不是引入可能会破坏现有程序的新关键字。不是没有考虑一个新的关键字-只是考虑到它的潜在破坏性,它不被认为是必需的。而直到ISO-C ++标准,这是声明的类型参数的唯一途径。

因此基本上Stroustrup重用了class关键字,而没有引入一个新关键字,该关键字随后在标准中由于以下原因而更改

作为给出的例子

template <class T>
class Demonstration {
public:
void method() {
    T::A *aObj; // oops …
     // …
};

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

typename T::A* a6;

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

由于关键字在工资单上,所以为什么不解决由最初决定重用class关键字引起的混乱

这就是为什么我们都

您可以看一下这篇文章,它一定会对您有帮助,我只是从中提取了尽可能多的内容


是的,但是typename如果您可以将现有关键字class用于相同目的,那么为什么需要一个新关键字?
杰斯珀,

5
@杰斯珀:我认为Xenus的回答在这里令人困惑。typename如Naveen的答案中所述,通过引用Josuttis,有必要解决解析问题。(我认为class在此位置插入a 不会起作用。)只有在这种情况下接受了new关键字之后,模板参数声明(还是该定义?)中才允许使用它,因为class始终存在一些误导。
09年

13

考虑代码

template<class T> somefunction( T * arg )
{
    T::sometype x; // broken
    .
    .

不幸的是,编译器并不需要心态,也不知道T :: sometype最终会引用类型名称还是T的静态成员。因此,有人typename告诉它:

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

6

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

例如

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

在此示例中,关键字typename对于代码进行编译是必需的。

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

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

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

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

(如果语法正确)。

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


另请参阅C ++ typename关键字的说明以获取更多(背景)信息。
Atafar 2015年

5

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

template<typename T>
struct test {
    typedef T* ptr;
};

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

有人可能会问,为什么这样做有用,并且确实如此:看起来真的没用。但是请记住,例如std::vector<bool>reference类型看起来与其他类型完全不同T。诚然,它不会将类型reference从类型更改为其他内容,但是仍然可能发生。

现在,如果您使用此test模板编写自己的模板,将会发生什么。像这样

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

您似乎还可以,因为您希望test<T>::ptr是一种类型。但是编译器不知道,事实上,甚至标准建议他甚至都期望相反,test<T>::ptr这不是一种类型。要告诉编译器您期望什么,您必须先添加一个typename。正确的模板如下所示

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

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


5

两种用途:

  1. 作为自template变量关键字(而不是class
  2. typename关键字告诉编译器的标识符是一种类型的(而不是静态成员变量)
template <typename T> class X  // [1]
{
    typename T::Y _member;  // [2] 
}

4

我认为所有答案都提到typename在两种不同情况下使用了关键字:

a)声明模板类型参数时。例如

template<class T> class MyClass{};        // these two cases are
template<typename T> class MyNewClass{};  // exactly the same.

它们之间没有区别,它们完全相同。

b)在为模板使用嵌套的依赖类型名称之前。

template<class T>
void foo(const T & param)
{
   typename T::NestedType * value; // we should use typename here
}

不使用typename会导致解析/编译错误。

正如Scot Meyers的书《Effective C ++》中提到的,我想添加到第二种情况的是,typename嵌套的依赖类型名称之前使用例外。例外是,如果您将嵌套的依赖类型名称用作基类或在成员初始化列表中,则不应在其中使用typename

template<class T>
class D : public B<T>::NestedType               // No need for typename here
{
public:
   D(std::string str) : B<T>::NestedType(str)   // No need for typename here
   {
      typename B<T>::AnotherNestedType * x;     // typename is needed here
   }
}

注意:使用typename用于所述第二情况下(即前嵌套依赖型名称)不因为C ++ 20需要的。


2
#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;
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.