有时我在使用模板时看到 gcc
吐出一些非常难以理解的错误消息...具体来说,我遇到了一些问题,看似正确的声明会导致非常奇怪的编译错误,通过前缀 typename
神奇地消失了声明开头的关键字...(例如,就在上周,我将两个迭代器声明为另一个模板类的成员,我必须这样做)...
typename
的故事是什么?
以下是 Josuttis 书中的引述:
引入关键字 typename 是为了指定后面的标识符是一个类型。考虑以下示例: template
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;
它指示编译器将后续语句视为声明。
既然关键字在工资单上,哎呀,为什么不解决最初决定重用类关键字引起的混乱。
这就是为什么我们两者都有
你可以看看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!
.
.
在某些情况下,您引用所谓的依赖类型的成员(意思是“依赖于模板参数”),编译器不能总是明确地推断出结果构造的语义含义,因为它不知道那是什么类型的名称(即它是一个类型的名称、一个数据成员的名称还是其他东西的名称)。在这种情况下,您必须通过明确告诉编译器该名称属于定义为该依赖类型的成员的类型名来消除歧义。
例如
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
类型看起来与其他 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
。 (当然,仅当您的模板的模板参数用于该内部模板时。)
两种用途:
作为模板参数关键字(而不是类) typename 关键字告诉编译器标识符是类型(而不是静态成员变量)
template
我认为所有答案都提到 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;
}
不定期副业成功案例分享
typename
的 C++20 dispenses with the need(尽管不是全部!)。