有时我看到gcc在使用模板时吐出的一些非常难以理解的错误消息...具体来说,我遇到了一些问题,看似正确的声明导致了非常奇怪的编译错误,这些错误通过在“typename”前加上神奇地消失了“关键字到声明的开头......(例如,就在上周,我将两个迭代器声明为另一个模板化类的成员,我必须这样做)...
typename的故事是什么?
答案 0 :(得分:173)
以下是Josuttis的书:
引入了关键字typename 指定那个标识符 以下是一种类型。考虑一下 以下示例:
template <class T> Class MyClass { typename T::SubType * ptr; ... };
这里,typename用于阐明这一点 SubType是类T的类型。因此, ptr是指向该类型的指针 T ::子类型。没有typename,SubType 将被视为静态成员。 因此
T::SubType * ptr
将是价值的倍增 类型为T的子类型,带有ptr。
答案 1 :(得分:31)
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;
它指示编译器将后续语句视为声明。
由于关键字位于工资核算上, 哎,为什么没有解决引起的混乱 由原来的决定来重用 class关键字。
这就是为什么我们两个都有
你可以看一下this post,它肯定会对你有所帮助,我只是尽可能地从它中提取
答案 2 :(得分: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!
.
.
答案 3 :(得分: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
的另一个角色将用于模板参数声明。
答案 4 :(得分: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 :(得分:4)
两种用途:
template <typename T> class X // [1] { typename T::Y _member; // [2] }
答案 6 :(得分: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;
}
答案 7 :(得分:2)
我认为所有答案都提到document.querySelector("#myNotes").style = JSON.parse(localStorage.userEdits);
关键字在两种不同情况下使用:
a)声明模板类型参数时。例如
typename
它们之间没有区别,而且完全相同。
b)在为模板使用 嵌套依赖类型名称 之前。
template<class T> class MyClass{}; // these two cases are
template<typename T> class MyNewClass{}; // exactly the same.
不使用template<class T>
void foo(const T & param)
{
typename T::NestedType * value; // we should use typename here
}
会导致解析/编译错误。
正如Scot Meyers的书Effective C++所述,我想添加到第二种情况是,在 嵌套依赖类型名称之前使用typename
是一个例外 。唯一的例外是,如果您将 嵌套依赖类型名称 用作基类或成员初始化列表 >,您不应在此处使用typename
:
typename
注意:自C ++ 20起,在第二种情况下(即在嵌套从属类型名称之前)不需要使用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
}
}
。