【发布时间】:2010-12-08 17:26:21
【问题描述】:
有时我在使用模板时看到gcc 吐出一些非常难以理解的错误消息...具体来说,我遇到了一些问题,看似正确的声明会导致非常奇怪的编译错误,这些错误会通过添加前缀神奇地消失声明开头的typename关键字...(例如,就在上周,我将两个迭代器声明为另一个模板类的成员,我必须这样做)...
typename 上的故事是什么?
【问题讨论】:
有时我在使用模板时看到gcc 吐出一些非常难以理解的错误消息...具体来说,我遇到了一些问题,看似正确的声明会导致非常奇怪的编译错误,这些错误会通过添加前缀神奇地消失声明开头的typename关键字...(例如,就在上周,我将两个迭代器声明为另一个模板类的成员,我必须这样做)...
typename 上的故事是什么?
【问题讨论】:
以下是 Josuttis 书中的引述:
关键字
typename被引入 指定标识符 以下是一种类型。考虑 下面的例子:template <class T> Class MyClass { typename T::SubType * ptr; ... };这里,
typename用来说明SubType是class T的一种。因此,ptr是指向类型的指针T::SubType。没有typename,SubType将被视为静态成员。 因此T::SubType * ptr将是价值的乘积
SubType类型为T和ptr。
【讨论】:
typename(虽然不是全部!)。
Stroustrup 重用现有的类 关键字指定类型参数 而不是引入一个新的关键字 这当然可能会破坏现有的 程式。这不是一个新的关键字 没有考虑——只是它 鉴于其没有被认为是必要的 潜在的破坏。 直到 ISO-C++ 标准,这是唯一的 声明类型参数的方法。
所以基本上 Stroustrup 重用了 class 关键字,而没有引入新的关键字,该关键字随后在标准中进行了更改,原因如下
作为给出的例子
template <class T>
class Demonstration {
public:
void method() {
T::A *aObj; // oops …
// …
};
语言语法将T::A *aObj; 误解为算术表达式,因此引入了一个名为typename 的新关键字
typename T::A* a6;
它指示编译器将后续语句视为声明。
由于关键字在工资单上, 哎呀,为什么不解决造成的混乱 根据最初的决定重用 类关键字。
这就是我们两者兼有的原因
你可以看看this post,一定对你有帮助,我只是尽可能地从中提取出来
【讨论】:
class 用于相同的目的,为什么还需要一个新的关键字 typename?
typename 必须通过引用 Josuttis 来解决 Naveen 的回答中描述的解析问题。 (我认为在这个地方插入 class 是行不通的。)只有在这种情况下接受了 new 关键字之后,它才允许在模板参数声明中使用(或者是定义吗?),因为那个class总有一些误导。
考虑代码
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!
.
.
【讨论】:
在某些情况下,您引用所谓的 dependent 类型的成员(意思是“依赖于模板参数”),编译器不能总是明确地推断出结果构造的语义含义,因为它不知道那是什么类型的名称(即它是类型名称、数据成员名称还是其他名称)。在这种情况下,您必须通过明确告诉编译器名称属于定义为该依赖类型的成员的类型名称来消除歧义。
例如
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的另一个作用是在模板参数声明中使用。
【讨论】:
秘密在于模板可以专门用于某些类型。这意味着它还可以为多种类型定义完全不同的接口。例如你可以写:
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 类型看起来与其他Ts 完全不同。诚然,它不会将 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。 (当然,只有当您的模板的模板参数用于该内部模板时。)
【讨论】:
两种用途:
template 参数关键字(而不是class)typename 关键字告诉编译器标识符是一种类型(而不是静态成员变量)template <typename T> class X // [1] { typename T::Y _member; // [2] }
【讨论】:
我认为所有答案都提到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
}
}
注意:从 C++20 开始,不需要在第二种情况下(即在嵌套的依赖类型名称之前)使用 typename。
【讨论】:
#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;
}
【讨论】: