c++ - 为什么必须在何处以及为什么要放置"template"和"typename"关键字?

标签 c++ templates typename c++-faq dependent-name

在模板中,为什么必须在何处以及为什么将typenametemplate放在从属名称上?无论如何,从属名称到底是什么?我有以下代码:

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>命名一个类型而不是一个函数?

最佳答案

为了解析C ++程序,编译器需要知道某些名称是否为类型。以下示例说明了这一点:

t * f;


应该如何解析?对于许多语言,编译器不需要知道名称的含义即可进行解析,并且基本上知道一行代码会执行什么操作。但是,在C ++中,根据t的含义,以上内容可能会产生截然不同的解释。如果是类型,则它将是指针f的声明。但是,如果不是类型,它将是一个乘法。因此,C ++标准在第(3/7)段中说:


  一些名称表示类型或模板。通常,无论何时遇到名称,都必须在继续解析包含该名称的程序之前确定该名称是否表示这些实体之一。确定此过程的过程称为名称查找。


如果t::x引用模板类型参数,编译器将如何找出名称t所指? x可以是可以乘以的静态int数据成员,也可以是可以产生声明的嵌套类或typedef。如果名称具有此属性-在知道实际的模板参数之前不能查找它-那么它被称为从属名称(它“取决于”模板参数)。

您可能建议仅等待用户实例化模板:


  让我们等到用户实例化模板,然后再找出t::x * f;的真实含义。


这将是可行的,并且实际上是标准允许的一种可能的实施方法。这些编译器基本上将模板的文本复制到内部缓冲区中,并且仅当需要实例化时,它们才会解析模板并可能检测到定义中的错误。但是,其他实现方式没有使模板的用户(可怜的同事!)受到模板作者的错误困扰,而是选择尽早检查模板,并在实例化发生之前尽快给出定义错误。

因此,必须有一种方法告诉编译器某些名称是类型,而某些名称不是。

“类型名称”关键字

答案是:我们决定编译器应如何解析它。如果t::x是从属名称,那么我们需要给它加上typename前缀,以告诉编译器以某种方式对其进行解析。标准在(14.6 / 2)中说:


  模板声明或定义中使用的依赖于模板参数的名称是
  假定不为类型命名,除非适用的名称查找找到类型名称或名称合格
  通过关键字typename。


许多名称不需要typename,因为编译器可以通过模板定义中的适用名称查找来弄清楚如何解析结构本身-例如,当T *f;是类型模板参数。但是要使T成为声明,必须将其写为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 t::x *f;-因此,可以认为,通常已知非合格名称总是引用类型。

正如介绍性文字所暗示的,对于表示模板的名称也存在类似的陷阱。

“模板”关键字

还记得上面的最初引用以及标准如何要求对模板进行特殊处理吗?让我们看下面的例子:

boost::function< int() > f;


对于人类读者来说,这似乎很明显。对于编译器则不是这样。想象一下typenameboost::function的以下任意定义:

namespace boost { int function = 0; }
int main() { 
  int f = 0;
  boost::function< int() > f; 
}


这实际上是一个有效的表达!它使用小于运算符将f与零(boost::function)进行比较,然后使用大于运算符将结果int()bool进行比较。但是,您可能知道,f in real life是模板,因此编译器知道(14.2 / 3):


  在名称查找(3.4)发现名称是模板名称之后,如果此名称后跟<,则<
  始终被视为模板参数列表的开头,而永远不被其后跟小于
  操作员。


现在我们回到与boost::function相同的问题。如果在解析代码时我们还不知道名称是否是模板怎么办?根据typename的指定,我们需要在模板名称之前插入template。看起来像:

t::template f<int>(); // call a function template


模板名称不仅可以在类成员访问中的14.2/4之后,而且可以在::->之后。您还需要在其中插入关键字:

this->template f<int>(); // call a function template




依存关系

对于那些架子上堆满Standardese书籍,又想知道我到底在说什么的人,我会说一些有关Standard中如何指定的内容。

在模板声明中,某些构造具有不同的含义,具体取决于用于实例化模板的模板参数:表达式可能具有不同的类型或值,变量可能具有不同的类型,或者函数调用最终可能会调用不同的函数。通常说这样的构建体取决于模板参数。

该标准通过构造是否依赖来精确定义规则。它将它们分成逻辑上不同的组:一个捕获类型,另一个捕获表达式。表达式可能取决于它们的值和/或类型。因此,我们附有典型示例:


依赖类型(例如:类型模板参数.
值相关的表达式(例如:非类型模板参数T
与类型相关的表达式(例如:转换为类型模板参数N


大多数规则是直观的,并且是递归建立的:例如,如果(T)0是值相关的表达式或T[N]是相关类型,则构造为N的类型是相关类型。有关详细信息,请参见T部分中的相关类型,有关类型相关的表达式,可参见(14.6.2/1,对于基于值的表达式,可参见(14.6.2.2)

相关名称

对于确切的从属名称,标准尚不清楚。简单阅读(您知道,最少惊奇的原理)后,它定义为从属名称的全部就是下面函数名称的特例。但是,由于显然还需要在实例化上下文中查找(14.6.2.3),因此它也必须是从属名称(幸运的是,自C ++ 14中期开始,委员会已开始研究如何解决此令人困惑的定义)。

为避免此问题,我已对标准文本进行了简单的解释。在表示依赖类型或表达式的所有构造中,它们的一个子集表示名称。因此,这些名称是“从属名称”。名称可以采用不同的形式-标准说:


  名称是指表示实体或标签(6.6.4,英文)的标识符(2.11),操作员功能ID(13.5),转换功能ID(12.3.2)或模板ID(14.2)的使用6.1)


标识符只是一个简单的字符/数字序列,接下来的两个是T::xoperator +形式。最后一种形式是operator type。所有这些都是名称,按照标准中的常规用法,名称还可以包含限定符,该限定符表示应查找名称的名称空间或类。

值相关表达式template-name <argument list>不是名称,而1 + N是名称。所有相关构造的子集即名称都称为相关名称。但是,函数名称在模板的不同实例中可能具有不同的含义,但不幸的是,该通用规则并未捕获它们。

从属函数名称

并不是本文的主要关注点,但仍然值得一提:函数名称是一个单独处理的异常。标识符函数名称不依赖于自身,而是依赖于调用中使用的依赖于类型的参数表达式。在示例N中,f((T)0)是从属名称。在标准中,这是在f中指定的。

附加说明和示例

在足够的情况下,我们同时需要(14.6.2/1)typename。您的代码应如下所示

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;


在某些情况下,关键字被禁止,如下所述


在从属基类的名称上,您不允许编写template。假定给定的名称是类类型名称。对于基类列表和构造函数初始化器列表中的名称都是如此:

 template <typename T>
 struct derive_from_Has_type : /* typename */ SomeBase<T>::type 
 { };

在using声明中,不可能在最后一个typename之后使用template,并且C ++委员会said不能使用解决方案。

 template <typename T>
 struct derive_from_Has_type : SomeBase<T> {
    using SomeBase<T>::template type; // error
    using typename SomeBase<T>::type; // typename *is* allowed
 };

关于c++ - 为什么必须在何处以及为什么要放置"template"和"typename"关键字?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19516832/

相关文章:

PHP在顺序包含文件时插入奇怪的空间

c++ - 如何散列 unordered_map?

c++ - 显式特化是使用部分特化语法

c++ - 直接访问唯一的类数据成员

c++ - 在这种情况下, “typename…”是什么意思?

c++ - 为什么 'typedef' 这个词后面需要 'typename' 作为依赖类型?

c++ - 为什么必须在哪里放置 “template”和 “typename”关键字?

c++ - 在 C++ 中为派生类定义问题

c++ - 非常量引用只能绑定(bind)到左值

java - 使用 t :pagelink and context 在 Java Tapestry 中创建 anchor 链接