在模板中,我必须在何处以及为什么必须将 typename
和 template
放在从属名称上?
究竟什么是从属名称?
我有以下代码:
template <typename T, typename Tail> // Tail will be a UnionNode too.
struct UnionNode : public Tail {
// ...
template<typename U> struct inUnion {
// Q: where to add typename/template here?
typedef Tail::inUnion<U> dummy;
};
template< > struct inUnion<T> {
};
};
template <typename T> // For the last node Tn.
struct UnionNode<T, void> {
// ...
template<typename U> struct inUnion {
char fail[ -2 + (sizeof(U)%2) ]; // Cannot be instantiated for any U
};
template< > struct inUnion<T> {
};
};
我遇到的问题在 typedef Tail::inUnion<U> dummy
行。我相当肯定 inUnion
是一个从属名称,VC++ 对此感到窒息是完全正确的。
我也知道我应该能够在某处添加 template
以告诉编译器 inUnion 是一个模板-ID。但具体在哪里?然后它是否应该假设 inUnion 是一个类模板,即 inUnion<U>
命名一个类型而不是一个函数?
char fail[ -sizeof(U) ]; // Cannot be instantiated for any U
不起作用,因为 -sizeof(U)
仍然总是积极的,所以它可能仍然适用于部分或全部 U
。
(见here also for my C++11 answer)
为了解析 C++ 程序,编译器需要知道某些名称是否是类型。以下示例说明:
t * f;
这应该如何解析?对于许多语言,编译器不需要知道名称的含义就可以解析并且基本上知道一行代码的作用。然而,在 C++ 中,根据 t
的含义,上述内容可能会产生截然不同的解释。如果它是一个类型,那么它将是一个指针 f
的声明。但是,如果它不是一个类型,它将是一个乘法。所以 C++ 标准在第 (3/7) 段中说:
一些名称表示类型或模板。通常,无论何时遇到一个名称,在继续解析包含它的程序之前,都必须确定该名称是否表示这些实体之一。确定这一点的过程称为名称查找。
如果 t
引用模板类型参数,编译器将如何找出名称 t::x
所引用的内容? x
可以是可以相乘的静态 int 数据成员,也可以同样是可以让步给声明的嵌套类或 typedef。 如果一个名称具有这个属性——在知道实际模板参数之前无法查找它——那么它被称为一个依赖名称(它“依赖于”模板参数)。
您可能会建议等到用户实例化模板:
让我们等到用户实例化模板,然后再找出t::x * f;的真正含义。
这将起作用,并且实际上被标准允许作为一种可能的实施方法。这些编译器基本上将模板的文本复制到内部缓冲区中,并且只有在需要实例化时,它们才会解析模板并可能检测定义中的错误。但是,与其用模板作者的错误来打扰模板的用户(可怜的同事!),其他实现选择在实例化之前尽早检查模板并在定义中尽快给出错误。
所以必须有一种方法告诉编译器某些名称是类型,而某些名称不是。
“类型名”关键字
答案是:我们决定编译器应该如何解析它。如果 t::x
是一个依赖名称,那么我们需要在它前面加上 typename
来告诉编译器以某种方式解析它。该标准在 (14.6/2) 处说:
在模板声明或定义中使用并且依赖于模板参数的名称被假定为不命名类型,除非适用的名称查找找到类型名称或该名称由关键字 typename 限定。
有许多名称不需要 typename
,因为编译器可以通过模板定义中适用的名称查找来确定如何解析构造本身 - 例如使用 T *f;
,当 T
是类型模板参数。但要使 t::x * f;
成为声明,它必须写成 typename t::x *f;
。如果省略关键字并且名称被视为非类型,但当实例化发现它表示类型时,编译器会发出通常的错误消息。有时,错误因此在定义时给出:
// t::x is taken as non-type, but as an expression the following misses an
// operator between the two names or a semicolon separating them.
t::x f;
语法允许 typename
仅在限定名称之前 - 因此,如果非限定名称这样做,则总是知道它们引用类型是理所当然的。
正如介绍性文本所暗示的,对于表示模板的名称也存在类似的问题。
“模板”关键字
还记得上面的初始报价以及标准如何要求对模板进行特殊处理吗?让我们看下面这个看似无辜的例子:
boost::function< int() > f;
对于人类读者来说,这可能看起来很明显。对于编译器来说不是这样。想象一下 boost::function
和 f
的以下任意定义:
namespace boost { int function = 0; }
int main() {
int f = 0;
boost::function< int() > f;
}
这实际上是一个有效的表达式!它使用小于运算符将 boost::function
与零 (int()
) 进行比较,然后使用大于运算符将结果 bool
与 f
进行比较。但是您可能很清楚,boost::function
in real life 是一个模板,因此编译器知道 (14.2/3):
在名称查找 (3.4) 发现一个名称是一个模板名称后,如果这个名称后跟一个 <,则 < 总是被视为模板参数列表的开头,而不是一个名称后跟 less-比运营商。
现在我们回到与 typename
相同的问题。如果我们在解析代码时还不知道名称是否是模板怎么办?我们需要在模板名称之前插入 template
,由 14.2/4
指定。这看起来像:
t::template f<int>(); // call a function template
在类成员访问中,模板名称不仅可以出现在 ::
之后,还可以出现在 ->
或 .
之后。您还需要在此处插入关键字:
this->template f<int>(); // call a function template
依赖项
对于那些在书架上有厚厚的标准书并且想知道我到底在说什么的人,我会谈谈标准中是如何规定的。
在模板声明中,某些构造具有不同的含义,具体取决于您用于实例化模板的模板参数:表达式可能具有不同的类型或值,变量可能具有不同的类型,或者函数调用可能最终调用不同的函数。这种结构通常被认为依赖于模板参数。
该标准通过构造是否依赖来精确定义规则。它将它们分成逻辑上不同的组:一个捕获类型,另一个捕获表达式。表达式可能取决于它们的值和/或它们的类型。所以我们有,附加了典型的例子:
依赖类型(例如:类型模板参数 T)
与值相关的表达式(例如:非类型模板参数 N)
类型相关的表达式(例如:转换为类型模板参数 (T)0)
大多数规则都是直观的,并且是递归构建的:例如,如果 N
是值依赖表达式或 T
是依赖类型,则构造为 T[N]
的类型是依赖类型。这方面的细节可以在 (14.6.2/1
) 部分中阅读,用于依赖类型,(14.6.2.2)
用于类型依赖表达式,(14.6.2.3)
用于值依赖表达式。
从属名称
标准有点不清楚究竟是什么依赖名称。在简单的阅读中(你知道,最不意外的原则),它定义为 从属名称 是下面函数名称的特殊情况。但由于显然 T::x
还需要在实例化上下文中查找,它也需要是一个从属名称(幸运的是,从 C++14 中期开始,委员会已经开始研究如何修复这个令人困惑的定义)。
为了避免这个问题,我对标准文本进行了简单的解释。在所有表示依赖类型或表达式的结构中,它们的一个子集表示名称。因此,这些名称是“从属名称”。一个名字可以有不同的形式——标准说:
名称是标识符 (2.11)、operator-function-id (13.5)、conversion-function-id (12.3.2) 或 template-id (14.2) 的使用,表示实体或标签 (6.6.4, 6.1)
标识符只是一个简单的字符/数字序列,而接下来的两个是 operator +
和 operator type
形式。最后一种形式是 template-name <argument list>
。所有这些都是名称,并且按照标准中的传统用法,名称还可以包含限定符,说明应该在哪个名称空间或类中查找名称。
值相关表达式 1 + N
不是名称,但 N
是。作为名称的所有依赖结构的子集称为依赖名称。然而,函数名称在模板的不同实例中可能具有不同的含义,但不幸的是,它并没有被这个一般规则所捕获。
依赖函数名
不是本文主要关注的问题,但仍然值得一提:函数名称是一个单独处理的异常。标识符函数名称不依赖于它本身,而是依赖于调用中使用的类型依赖的参数表达式。在示例 f((T)0)
中,f
是从属名称。在标准中,这在 (14.6.2/1)
处指定。
附加说明和示例
在足够多的情况下,我们同时需要 typename
和 template
。您的代码应如下所示
template <typename T, typename Tail>
struct UnionNode : public Tail {
// ...
template<typename U> struct inUnion {
typedef typename Tail::template inUnion<U> dummy;
};
// ...
};
关键字 template
不必总是出现在名称的最后部分。它可以出现在用作作用域的类名之前的中间,如下例所示
typename t::template iterator<int>::value_type v;
在某些情况下,关键字是被禁止的,详情如下
在依赖基类的名称上,您不能写 typename。假定给定的名称是类类型名称。对于基类列表和构造函数初始化列表中的名称都是如此: template
在 using-declarations 中,不能在最后一个 :: 之后使用模板,并且 C++ 委员会表示不要制定解决方案。模板
C++11
问题
虽然 C++03 中关于何时需要 typename
和 template
的规则在很大程度上是合理的,但它的表述有一个令人讨厌的缺点
template<typename T>
struct A {
typedef int result_type;
void f() {
// error, "this" is dependent, "template" keyword needed
this->g<float>();
// OK
g<float>();
// error, "A<T>" is dependent, "typename" keyword needed
A<T>::result_type n1;
// OK
result_type n2;
}
template<typename U>
void g();
};
可以看出,我们需要消歧关键字,即使编译器可以完美地判断出 A::result_type
只能是 int
(因此是一种类型),并且 this->g
只能是成员模板 g
稍后声明(即使 A
在某处显式特化,也不会影响该模板中的代码,因此它的含义不会受到 A
以后特化的影响!)。
当前实例化
为了改善这种情况,在 C++11 中,语言会跟踪类型何时引用封闭模板。要知道,该类型必须是使用某种形式的名称形成的,也就是它自己的名称(在上面,A
、A<T>
、::A<T>
)。由这样的名称引用的类型被称为当前实例化。如果形成名称的类型是成员/嵌套类(则 A::NestedClass
和 A
都是当前实例化),则可能有多个类型都是当前实例化。
基于这个概念,该语言表示 CurrentInstantiation::Foo
、Foo
和 CurrentInstantiationTyped->Foo
(例如 A *a = this; a->Foo
)都是 当前实例化的成员 如果它们被发现是当前实例化的类或其非依赖基类之一的成员(只需立即进行名称查找)。
如果限定符是当前实例化的成员,现在不再需要关键字 typename
和 template
。这里要记住的一个关键点是 A<T>
仍然是一个依赖于类型的名称(毕竟 T
也是依赖于类型的)。但是 A<T>::result_type
被认为是一种类型——编译器会“神奇地”研究这种依赖类型来解决这个问题。
struct B {
typedef int result_type;
};
template<typename T>
struct C { }; // could be specialized!
template<typename T>
struct D : B, C<T> {
void f() {
// OK, member of current instantiation!
// A::result_type is not dependent: int
D::result_type r1;
// error, not a member of the current instantiation
D::questionable_type r2;
// OK for now - relying on C<T> to provide it
// But not a member of the current instantiation
typename D::questionable_type r3;
}
};
这令人印象深刻,但我们能做得更好吗?该语言甚至更进一步,要求实现在实例化 D::f
时再次查找 D::result_type
(即使它在定义时已经找到了它的含义)。当现在查找结果不同或导致歧义时,程序格式错误,必须给出诊断。想象一下如果我们这样定义 C
会发生什么
template<>
struct C<int> {
typedef bool result_type;
typedef int questionable_type;
};
实例化 D<int>::f
时需要编译器来捕获错误。因此,您将获得两全其美:“延迟”查找保护您,如果您可能遇到依赖基类的麻烦,以及“立即”查找,可以将您从 typename
和 template
中解放出来。
未知专业
在 D
的代码中,名称 typename D::questionable_type
不是当前实例化的成员。相反,该语言将其标记为未知专业的成员。特别是,当您执行 DependentTypeName::Foo
或 DependentTypedName->Foo
并且依赖类型不是 当前实例时,总是会出现这种情况(在这种情况下,编译器可以放弃并说“我们将稍后看看 Foo
是什么)或者它 是 当前实例化并且在它或其非依赖基类中找不到名称,并且还有依赖基类。
想象一下如果我们在上面定义的 A
类模板中有一个成员函数 h
会发生什么
void h() {
typename A<T>::questionable_type x;
}
在 C++03 中,语言允许捕获此错误,因为永远不会有有效的方法来实例化 A<T>::h
(无论您给 T
提供什么参数)。在 C++11 中,该语言现在有进一步的检查,以便为编译器提供更多实现此规则的理由。由于 A
没有依赖的基类,并且 A
没有声明任何成员 questionable_type
,因此名称 A<T>::questionable_type
既不是当前实例化的成员 也不是未知专业的成员。在这种情况下,该代码应该无法在实例化时有效编译,因此该语言禁止限定符是当前实例化的名称既不是未知特化的成员也不是当前实例化的成员(但是,这种违规仍然不需要诊断)。
例子和琐事
您可以在 this answer 上尝试这些知识,并查看上述定义在实际示例中是否对您有意义(在该答案中重复的细节略少)。
C++11 规则使以下有效的 C++03 代码格式错误(这不是 C++ 委员会的意图,但可能不会被修复)
struct B { void f(); };
struct A : virtual B { void f(); };
template<typename T>
struct C : virtual B, T {
void g() { this->f(); }
};
int main() {
C<A> c; c.g();
}
这个有效的 C++03 代码将在实例化时将 this->f
绑定到 A::f
,一切都很好。然而,C++11 立即将其绑定到 B::f
并在实例化时需要仔细检查,检查查找是否仍然匹配。但是,在实例化 C<A>::g
时,会应用 Dominance Rule,并且查找会改为找到 A::f
。
前言 这篇文章旨在成为 litb 文章的易于阅读的替代品。
根本目的是相同的;对“什么时候?”的解释“为什么?”必须应用 typename 和 template。
类型名和模板的目的是什么?
typename
和 template
在声明模板时以外的情况下可用。
在 C++ 中的某些上下文中,必须明确告诉编译器如何处理名称,所有这些上下文都有一个共同点;它们至少依赖于一个模板参数。
我们将此类名称称为解释中可能存在歧义的名称,例如: “依赖名称”。
这篇文章将解释从属名称和两个关键字之间的关系。
一个片段说超过 1000 个单词
尝试向您自己、朋友或您的猫解释以下函数模板中发生的事情;标记为 (A) 的陈述中发生了什么?
template<class T> void f_tmpl () { T::foo * x; /* <-- (A) */ }
这可能并不像人们想象的那么简单,更具体地说,评估 (A) 的结果在很大程度上取决于作为模板参数 T
传递的类型。
不同的 T
可以极大地改变所涉及的语义。
struct X { typedef int foo; }; /* (C) --> */ f_tmpl<X> ();
struct Y { static int const foo = 123; }; /* (D) --> */ f_tmpl<Y> ();
两种不同的场景:
如果我们用类型 X 实例化函数模板,如在 (C) 中,我们将声明一个名为 x 的指向 int 的指针,但是;
如果我们用类型 Y 实例化模板,如 (D),则 (A) 将由一个表达式组成,该表达式计算 123 与某个已声明的变量 x 的乘积。
基本原理
C++ 标准关心我们的安全和幸福,至少在这种情况下是这样。
为了防止实现可能遭受令人讨厌的意外,标准要求我们通过在我们希望将名称视为类型名称或模板的任何地方明确说明意图来解决依赖名称的歧义 - ID。
如果没有说明,则从属名称将被视为变量或函数。
如何处理依赖名称?
如果这是一部好莱坞电影,从属名字将是通过身体接触传播的疾病,立即影响其宿主使其混乱。可能会导致不正确的人,erhm..程序的混乱。
从属名称是直接或间接依赖于模板参数的任何名称。
template<class T> void g_tmpl () {
SomeTrait<T>::type foo; // (E), ill-formed
SomeTrait<T>::NestedTrait<int>::type bar; // (F), ill-formed
foo.data<int> (); // (G), ill-formed
}
我们在上面的代码片段中有四个从属名称:
E) “类型”取决于 SomeTrait
“类型”取决于 SomeTrait
F) "NestedTrait",它是一个模板 ID,依赖于 SomeTrait
"NestedTrait",它是一个模板 ID,依赖于 SomeTrait
(F) 末尾的“type”依赖于 NestedTrait,它依赖于 SomeTrait
G) “data”,看起来像一个成员函数模板,间接地是一个从属名称,因为 foo 的类型取决于 SomeTrait
“data”,看起来像一个成员函数模板,间接地是一个从属名称,因为 foo 的类型取决于 SomeTrait
如果编译器将依赖名称解释为变量/函数,则语句 (E)、(F) 或 (G) 均无效(如前所述,如果我们没有明确说明会发生这种情况)。
解决方案
为了使 g_tmpl
有一个有效的定义,我们必须明确告诉编译器我们期望一个类型在 (E)、一个 template-id 和一个 type< /em> 在 (F) 中,template-id 在 (G) 中。
template<class T> void g_tmpl () {
typename SomeTrait<T>::type foo; // (G), legal
typename SomeTrait<T>::template NestedTrait<int>::type bar; // (H), legal
foo.template data<int> (); // (I), legal
}
每次 name 表示一个类型时,all names 必须是 type-names 或 namespaces ,考虑到这一点,很容易看出我们在完全限定名称的开头应用了 typename
。
template
然而,在这方面是不同的,因为没有办法得出这样的结论; “哦,这是一个模板,那么这个其他的东西也必须是一个模板”。这意味着我们将 template
直接应用在我们想要处理的任何 name 前面。
我可以将关键字放在任何名称的前面吗?
“我可以在任何名称前面加上类型名和模板吗?我不想担心它们出现的上下文......” - 一些 C++ 开发人员
标准中的规则规定,只要您处理限定名称 (K),就可以应用关键字,但如果名称不限定,则应用程序格式错误 (L)。
namespace N {
template<class T>
struct X { };
}
N:: X<int> a; // ... legal
typename N::template X<int> b; // (K), legal
typename template X<int> c; // (L), ill-formed
注意:在不需要的情况下应用 typename
或 template
不是好的做法;仅仅因为你可以做某事,并不意味着你应该这样做。
此外,在某些情况下,typename
和 template
被明确 禁止:
当指定类继承的基类时,派生类的基说明符列表中的每个名称都已被视为类型名称,显式指定类型名称既不正确,又是多余的。 // .------- base-specifier-list template
当模板 ID 是派生类的 using-directive struct Base { template
这个答案旨在回答(部分)标题问题。如果您需要更详细的答案来解释为什么必须将它们放在那里,请转到 here。
放置 typename
关键字的一般规则主要是在您使用模板参数并且想要访问嵌套的 typedef
或使用别名时,例如:
template<typename T>
struct test {
using type = T; // no typename required
using underlying_type = typename T::type // typename required
};
请注意,这也适用于元函数或采用通用模板参数的东西。但是,如果提供的模板参数是显式类型,则不必指定 typename
,例如:
template<typename T>
struct test {
// typename required
using type = typename std::conditional<true, const T&, T&&>::type;
// no typename required
using integer = std::conditional<true, int, float>::type;
};
添加 template
限定符的一般规则大多相似,只是它们通常涉及本身模板化的结构/类的模板化成员函数(静态或其他),例如:
鉴于此结构和功能:
template<typename T>
struct test {
template<typename U>
void get() const {
std::cout << "get\n";
}
};
template<typename T>
void func(const test<T>& t) {
t.get<int>(); // error
}
尝试从函数内部访问 t.get<int>()
将导致错误:
main.cpp:13:11: error: expected primary-expression before 'int'
t.get<int>();
^
main.cpp:13:11: error: expected ';' before 'int'
因此,在这种情况下,您需要事先使用 template
关键字并像这样调用它:
t.template get<int>()
这样编译器将正确解析它而不是 t.get < int
。
typedef typename Tail::inUnion<U> dummy;
但是,我不确定您对 inUnion 的实施是否正确。如果我理解正确,这个类不应该被实例化,因此“失败”选项卡永远不会失败。也许最好用一个简单的布尔值来指示类型是否在联合中。
template <typename T, typename TypeList> struct Contains;
template <typename T, typename Head, typename Tail>
struct Contains<T, UnionNode<Head, Tail> >
{
enum { result = Contains<T, Tail>::result };
};
template <typename T, typename Tail>
struct Contains<T, UnionNode<T, Tail> >
{
enum { result = true };
};
template <typename T>
struct Contains<T, void>
{
enum { result = false };
};
PS:看看Boost::Variant
PS2:看看 typelists,特别是在 Andrei Alexandrescu 的书:Modern C++ Design
C++20 又名 C++2a
如本 Proposal 所述,C++20 / C++2a 进一步放宽了对 typename
关键字的要求。特别是,typename
现在可以在所有那些在语法上只有一个类型是合法的地方被省略。因此,如果未知标记必须是类型,C++20 实际上会将其视为类型。但是,为了向后兼容,仍然可以使用 typename
。
特别是,大多数 using
和 typedef
声明现在可以在没有 typename
的情况下编写。 typename
也可以在方法返回类型的声明(包括尾随返回类型)、方法和 lambda 参数的声明以及 static_cast
、const_cast
、dynamic_cast
和 reinterpret_cast
的类型参数中省略.
一个值得注意的例外,仍然需要 typename
,是在用户或库定义模板的实例化参数列表中:即使该特定参数被声明为类型,仍然需要 typename
关键字。所以 static_cast<A::B>(arg)
在 C++20 中是合法的,但如果 A 是依赖范围并且 my_template_class
需要一个类型,则 my_template_class<A::B>(arg)
是非良构的。
几个例子:
class A { public: typedef int type; static const int val { 1 }; };
class B { public: typedef float type; static const int val { 2 }; };
template<typename T> class C {};
template<int I> class D {};
template<typename T> class X {
T::type v; // OK
T::type f(T::type arg) { return arg; } // OK
T::type g(double arg) { return static_cast<T::type>(arg); } // OK
// C<T::type> c1; // error
D<T::val> d; // OK (as has always been)
C<typename T::type> c2; // OK (old style)
typedef T::type mytype; // OK
using mytypeagain = T::type; // OK
C<mytype> c3; // OK (via typedef / using)
};
X<A> xa;
X<B> xb;
template
解析器指南在相同的上下文中是可选的。
我将 JLBorges 出色的 response 逐字放在 cplusplus.com 的一个类似问题上,因为这是我读过的关于该主题的最简洁的解释。
在我们编写的模板中,可以使用两种名称——从属名称和非从属名称。依赖名称是依赖于模板参数的名称;无论模板参数是什么,非依赖名称都具有相同的含义。例如: template< typename T > void foo( T& x, std::string str, int count ) { // 在第二阶段查找这些名称 // 当 foo 被实例化并且类型 T 已知 x.size (); // 依赖名称(非类型) T::instance_count ; // 依赖名称(非类型) typename T::iterator i ; // 依赖名称(类型) // 在第一阶段, // T::instance_count 被视为非类型(这是默认值) // typename 关键字指定 T::iterator 将被视为类型。 // 这些名称在第一阶段查找 std::string::size_type s ; // 非依赖名称(类型) std::string::npos ; // 非依赖名称(非类型) str.empty() ; // 非依赖名称(非类型) count ; // 非依赖名称(非类型) } 对于模板的每个不同实例化,依赖名称所指的内容可能有所不同。因此,C++ 模板受制于“两阶段名称查找”。当最初解析模板时(在任何实例化发生之前),编译器会查找非依赖名称。当模板的特定实例化发生时,模板参数是已知的,编译器会查找相关名称。在第一阶段,解析器需要知道依赖名称是类型的名称还是非类型的名称。默认情况下,从属名称假定为非类型的名称。从属名称之前的 typename 关键字指定它是类型的名称。
概括
仅在模板声明和定义中使用关键字 typename,前提是您具有引用类型并依赖于模板参数的限定名称。
依赖名称是依赖于模板参数的名称,我们需要指示编译器以便在实际调用模板类/函数之前正确编译它们。
typename -> 告诉编译器依赖的名字是一个实际的类型 template
template -> 告诉编译器依赖的名字是一个模板函数/类 template
typename
,即使此时语法不允许除类型名之外的其他解释?