我必须将“模板”放在哪里以及为什么? 和“类型名称” 关键词?

发布于 2024-07-14 07:52:17 字数 1056 浏览 16 评论 0原文

在模板中,我必须在何处以及为何将 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; // intentionally not defined
    template< > struct inUnion<T> { };   // specialization only for T
};

我遇到的问题是在 typedef Tail::inUnion 中。 虚拟线。 我相当确定 inUnion 是一个从属名称,VC++ 对此感到窒息,这是完全正确的。

我还知道我应该能够在某处添加 template 来告诉编译器 inUnion 是一个 template-id,但具体在哪里呢? 那么是否应该假设 inUnion 是一个类模板,即 inUnion 命名一个类型而不是一个函数?

In templates, where and why do I have to put typename and template on dependent names?
What exactly are dependent names anyway?

I have the following code:

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; // intentionally not defined
    template< > struct inUnion<T> { };   // specialization only for T
};

The problem I have is in the typedef Tail::inUnion<U> dummy line. I'm fairly certain that inUnion is a dependent name, and VC++ is quite right in choking on it.

I also know that I should be able to add template somewhere to tell the compiler that inUnion is a template-id, but where exactly? Should it then assume that inUnion is a class template, i.e. inUnion<U> names a type and not a function?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

顾冷 2024-07-21 07:52:17

(请参阅这里也是我的 C++11 答案

为了解析 C++ 程序,编译器需要知道是否某些名称是否是类型。 下面的例子演示了:

t * f;

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

一些名称表示类型或模板。 一般来说,每当遇到一个名称时,都需要在继续解析包含该名称的程序之前确定该名称是否表示这些实体之一。 确定这一点的过程称为名称查找。

如果 t 引用模板类型参数,编译器如何找出名称 t::x 引用的内容? x 可以是可以相乘的静态 int 数据成员,也可以同样是可以生成声明的嵌套类或 typedef。 如果名称具有此属性 - 在知道实际模板参数之前无法查找它 - 那么它被称为依赖名称(它“依赖于”模板参数)。

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

等用户实例化模板后,再了解t::x * f;的真正含义。

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

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

“typename”关键字

答案是:我们决定编译器应如何解析它。 如果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 - 因此理所当然地认为非限定名称始终引用类型如果他们这样做的话。

正如介绍性文本所暗示的,表示模板的名称也存在类似的问题。

“template”关键字

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

boost::function< int() > f;

对于人类读者来说,它可能看起来很明显。 对于编译器来说并非如此。 想象一下 boost::functionf 的以下任意定义:

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

这实际上是一个有效的表达式! 它使用小于运算符将 boost::function 与零进行比较 (int()),然后使用大于运算符比较结果 boolf 相对应。 然而,正如您所知, boost::function 在现实生活中是一个模板,因此编译器知道(14.2/3):

名称查找(3.4)后发现名称是模板名称,如果该名称后面跟着 <,则 < 是
始终被视为模板参数列表的开头,而不是作为名称后跟小于号
运算符。

现在我们又回到了与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

大多数规则都很直观,并且是递归构建的:例如,构造为 T[N 的类型如果 N 是值相关表达式或 T 是相关类型,则 ] 是相关类型。 有关详细信息,请参阅 (14.6.2/1) 部分(了解相关类型)、(14.6.2.2)(了解类型相关表达式)和 (14.6 .2.3) 用于值相关表达式。

从属名称

标准对于确切什么是从属名称有点不清楚。 简单阅读一下(您知道,最小惊喜原则),它定义为依赖名称的所有内容都是下面函数名称的特殊情况。 但由于显然 T::x 也需要在实例化上下文中查找,因此它也需要是一个依赖名称(幸运的是,从 C++14 中期开始,委员会已经开始研究如何解决这个令人困惑的定义)。

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

名称是使用标识符 (2.11)、运算符函数 id (13.5)、转换函数 id (12.3.2) 或模板 id (14.2) 表示实体或标签 (6.6) .4, 6.1)

标识符只是一个简单的字符/数字序列,而接下来的两个是operator +operator type形式。 最后一种形式是template-name。 所有这些都是名称,并且按照标准中的常规使用,名称还可以包含限定符,说明应在哪个名称空间或类中查找名称。

值依赖表达式 1 + N 不是名称,但 N 是。 所有依赖结构(名称)的子集称为依赖名称。 然而,函数名称在模板的不同实例中可能具有不同的含义,但不幸的是,不符合这一一般规则。

依赖函数名称

不是本文主要关注的问题,但仍然值得一提:函数名称是单独处理的例外。 标识符函数名称不依赖于其本身,而是依赖于调用中使用的类型相关参数表达式。 在示例 f((T)0) 中,f 是从属名称。 在标准中,这是在 (14.6.2/1) 中指定的。

其他注释和示例

在足够多的情况下,我们需要 typenametemplate。 您的代码应如下所示

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;

在某些情况下,关键字是被禁止的,如下详述

  • 在依赖基类的名称上,您不允许编写类型名称。 假定给定的名称是类类型名称。 对于基类列表和构造函数初始值设定项列表中的名称都是如此:

     模板  
       structderive_from_Has_type : /* 类型名 */ SomeBase::type  
       { }; 
      
  • 在 using 声明中,不可能在最后一个 :: 之后使用 template ,并且 C++ 委员会不起作用上一个解决方案。

     模板  
       结构derive_from_Has_type:SomeBase   { 
          使用 SomeBase:: 模板类型;   // 错误 
          使用类型名 SomeBase::type;   // 类型名*是*允许的 
       }; 
      

(See here also for my C++11 answer)

In order to parse a C++ program, the compiler needs to know whether certain names are types or not. The following example demonstrates that:

t * f;

How should this be parsed? For many languages a compiler doesn't need to know the meaning of a name in order to parse and basically know what action a line of code does. In C++, the above however can yield vastly different interpretations depending on what t means. If it's a type, then it will be a declaration of a pointer f. However if it's not a type, it will be a multiplication. So the C++ Standard says at paragraph (3/7):

Some names denote types or templates. In general, whenever a name is encountered it is necessary to determine whether that name denotes one of these entities before continuing to parse the program that contains it. The process that determines this is called name lookup.

How will the compiler find out what a name t::x refers to, if t refers to a template type parameter? x could be a static int data member that could be multiplied or could equally well be a nested class or typedef that could yield to a declaration. If a name has this property - that it can't be looked up until the actual template arguments are known - then it's called a dependent name (it "depends" on the template parameters).

You might recommend to just wait till the user instantiates the template:

Let's wait until the user instantiates the template, and then later find out the real meaning of t::x * f;.

This will work and actually is allowed by the Standard as a possible implementation approach. These compilers basically copy the template's text into an internal buffer, and only when an instantiation is needed, they parse the template and possibly detect errors in the definition. But instead of bothering the template's users (poor colleagues!) with errors made by a template's author, other implementations choose to check templates early on and give errors in the definition as soon as possible, before an instantiation even takes place.

So there has to be a way to tell the compiler that certain names are types and that certain names aren't.

The "typename" keyword

The answer is: We decide how the compiler should parse this. If t::x is a dependent name, then we need to prefix it by typename to tell the compiler to parse it in a certain way. The Standard says at (14.6/2):

A name used in a template declaration or definition and that is dependent on a template-parameter is
assumed not to name a type unless the applicable name lookup finds a type name or the name is qualified
by the keyword typename.

There are many names for which typename is not necessary, because the compiler can, with the applicable name lookup in the template definition, figure out how to parse a construct itself - for example with T *f;, when T is a type template parameter. But for t::x * f; to be a declaration, it must be written as typename t::x *f;. If you omit the keyword and the name is taken to be a non-type, but when instantiation finds it denotes a type, the usual error messages are emitted by the compiler. Sometimes, the error consequently is given at definition time:

// 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;

The syntax allows typename only before qualified names - it is therefor taken as granted that unqualified names are always known to refer to types if they do so.

A similar gotcha exists for names that denote templates, as hinted at by the introductory text.

The "template" keyword

Remember the initial quote above and how the Standard requires special handling for templates as well? Let's take the following innocent-looking example:

boost::function< int() > f;

It might look obvious to a human reader. Not so for the compiler. Imagine the following arbitrary definition of boost::function and f:

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

That's actually a valid expression! It uses the less-than operator to compare boost::function against zero (int()), and then uses the greater-than operator to compare the resulting bool against f. However as you might well know, boost::function in real life is a template, so the compiler knows (14.2/3):

After name lookup (3.4) finds that a name is a template-name, if this name is followed by a <, the < is
always taken as the beginning of a template-argument-list and never as a name followed by the less-than
operator.

Now we are back to the same problem as with typename. What if we can't know yet whether the name is a template when parsing the code? We will need to insert template immediately before the template name, as specified by 14.2/4. This looks like:

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

Template names can not only occur after a :: but also after a -> or . in a class member access. You need to insert the keyword there too:

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

Dependencies

For the people that have thick Standardese books on their shelf and that want to know what exactly I was talking about, I'll talk a bit about how this is specified in the Standard.

In template declarations some constructs have different meanings depending on what template arguments you use to instantiate the template: Expressions may have different types or values, variables may have different types or function calls might end up calling different functions. Such constructs are generally said to depend on template parameters.

The Standard defines precisely the rules by whether a construct is dependent or not. It separates them into logically different groups: One catches types, another catches expressions. Expressions may depend by their value and/or their type. So we have, with typical examples appended:

  • Dependent types (e.g: a type template parameter T)
  • Value-dependent expressions (e.g: a non-type template parameter N)
  • Type-dependent expressions (e.g: a cast to a type template parameter (T)0)

Most of the rules are intuitive and are built up recursively: For example, a type constructed as T[N] is a dependent type if N is a value-dependent expression or T is a dependent type. The details of this can be read in section (14.6.2/1) for dependent types, (14.6.2.2) for type-dependent expressions and (14.6.2.3) for value-dependent expressions.

Dependent names

The Standard is a bit unclear about what exactly is a dependent name. On a simple read (you know, the principle of least surprise), all it defines as a dependent name is the special case for function names below. But since clearly T::x also needs to be looked up in the instantiation context, it also needs to be a dependent name (fortunately, as of mid C++14 the committee has started to look into how to fix this confusing definition).

To avoid this problem, I have resorted to a simple interpretation of the Standard text. Of all the constructs that denote dependent types or expressions, a subset of them represent names. Those names are therefore "dependent names". A name can take different forms - the Standard says:

A name is a use of an identifier (2.11), operator-function-id (13.5), conversion-function-id (12.3.2), or template-id (14.2) that denotes an entity or label (6.6.4, 6.1)

An identifier is just a plain sequence of characters / digits, while the next two are the operator + and operator type form. The last form is template-name <argument list>. All these are names, and by conventional use in the Standard, a name can also include qualifiers that say what namespace or class a name should be looked up in.

A value dependent expression 1 + N is not a name, but N is. The subset of all dependent constructs that are names is called dependent name. Function names, however, may have different meaning in different instantiations of a template, but unfortunately are not caught by this general rule.

Dependent function names

Not primarily a concern of this article, but still worth mentioning: Function names are an exception that are handled separately. An identifier function name is dependent not by itself, but by the type dependent argument expressions used in a call. In the example f((T)0), f is a dependent name. In the Standard, this is specified at (14.6.2/1).

Additional notes and examples

In enough cases we need both of typename and template. Your code should look like the following

template <typename T, typename Tail>
struct UnionNode : public Tail {
    // ...
    template<typename U> struct inUnion {
        typedef typename Tail::template inUnion<U> dummy;
    };
    // ...
};

The keyword template doesn't always have to appear in the last part of a name. It can appear in the middle before a class name that's used as a scope, like in the following example

typename t::template iterator<int>::value_type v;

In some cases, the keywords are forbidden, as detailed below

  • On the name of a dependent base class you are not allowed to write typename. It's assumed that the name given is a class type name. This is true for both names in the base-class list and the constructor initializer list:

     template <typename T>
     struct derive_from_Has_type : /* typename */ SomeBase<T>::type 
     { };
    
  • In using-declarations it's not possible to use template after the last ::, and the C++ committee said not to work on a solution.

     template <typename T>
     struct derive_from_Has_type : SomeBase<T> {
        using SomeBase<T>::template type; // error
        using typename SomeBase<T>::type; // typename *is* allowed
     };
    
☆獨立☆ 2024-07-21 07:52:17

C++11

问题

虽然 C++03 中关于何时需要 typenametemplate 的规则在很大程度上是合理的,但其表述有一个令人烦恼的缺点,

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 中,语言会跟踪类型何时引用封闭模板。 要知道,类型必须是通过使用某种形式的名称来形成的,该名称就是它自己的名称(上面的 AA、<代码>::A)。 由此类名称引用的类型被称为当前实例化。 如果形成名称的类型是成员/嵌套类(则 A::NestedClassA 是),则可能有多种类型都是当前实例化两个当前实例)。

基于这个概念,该语言表示 CurrentInstantiation::FooFooCurrentInstantiationTyped->Foo(例如 A * a = this; a->Foo) 都是当前实例化的成员 如果它们被发现是当前实例化的类的成员或其非依赖基类之一(只需立即进行名称查找)。

如果限定符是当前实例化的成员,则现在不再需要关键字 typenametemplate。 这里要记住的一个关键点是,A仍然是一个依赖于类型的名称(毕竟T也依赖于类型)。 但是 A::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::f 时,编译器需要捕获错误。 因此,您可以充分利用两个世界:“延迟”查找可以在您因依赖基类而遇到麻烦时提供保护,而“立即”查找可以使您从 typenametemplate 中解放出来

未知的特化

D 的代码中,名称 typename D::questionable_type 不是当前实例化的成员。 相反,该语言将其标记为未知专业化的成员。 特别是,当您执行 DependentTypeName::FooDependentTypedName->Foo 并且依赖类型为不是时,情况总是如此当前实例化(在这种情况下,编译器可以放弃并说“我们稍后会看看 Foo 是什么)”或者它当前实例化,并且在它或其非依赖基类,并且还有依赖基类,

A 类模板中有一个成员函数 h 会发生什么

void h() {
  typename A<T>::questionable_type x;
}

想象一下如果我们在上面定义的C+ 中的 。 +03,该语言允许捕获此错误,因为永远不可能有有效的方法来实例化 A::h (无论您向 T 提供什么参数)。在 C++11 中,该语言现在进行了进一步检查,以便为编译器实现此规则提供更多理由,因为 A 没有依赖基类,并且 A 声明没有。成员 questionable_type,名称 A::questionable_type 既不是当前实例化的成员,也不是未知专业的成员。 在这种情况下,该代码应该无法在实例化时有效编译,因此该语言禁止限定符为当前实例化的名称既不是未知专业化的成员,也不是当前实例化的成员(但是,这种违规仍然不需要诊断)。

示例和琐事

您可以在此答案上尝试这些知识,看看上述定义在现实世界中是否对您有意义示例(在该答案中重复的内容稍微不太详细)。

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-> ;fA::f 在实例化时一切都很好。 然而,C++11 立即将其绑定到 B::f 并在实例化时需要进行双重检查,检查查找是否仍然匹配。 但是,在实例化 C::g 时,支配规则适用,查找将找到A::f

C++11

Problem

While the rules in C++03 about when you need typename and template are largely reasonable, there is one annoying disadvantage of its formulation

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();
};

As can be seen, we need the disambiguation keyword even if the compiler could perfectly figure out itself that A::result_type can only be int (and is hence a type), and this->g can only be the member template g declared later (even if A is explicitly specialized somewhere, that would not affect the code within that template, so its meaning cannot be affected by a later specialization of A!).

Current instantiation

To improve the situation, in C++11 the language tracks when a type refers to the enclosing template. To know that, the type must have been formed by using a certain form of name, which is its own name (in the above, A, A<T>, ::A<T>). A type referenced by such a name is known to be the current instantiation. There may be multiple types that are all the current instantiation if the type from which the name is formed is a member/nested class (then, A::NestedClass and A are both current instantiations).

Based on this notion, the language says that CurrentInstantiation::Foo, Foo and CurrentInstantiationTyped->Foo (such as A *a = this; a->Foo) are all member of the current instantiation if they are found to be members of a class that is the current instantiation or one of its non-dependent base classes (by just doing the name lookup immediately).

The keywords typename and template are now not required anymore if the qualifier is a member of the current instantiation. A keypoint here to remember is that A<T> is still a type-dependent name (after all T is also type dependent). But A<T>::result_type is known to be a type - the compiler will "magically" look into this kind of dependent types to figure this out.

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;        
  }
};

That's impressive, but can we do better? The language even goes further and requires that an implementation again looks up D::result_type when instantiating D::f (even if it found its meaning already at definition time). When now the lookup result differs or results in ambiguity, the program is ill-formed and a diagnostic must be given. Imagine what happens if we defined C like this

template<>
struct C<int> {
  typedef bool result_type;
  typedef int questionable_type;
};

A compiler is required to catch the error when instantiating D<int>::f. So you get the best of the two worlds: "Delayed" lookup protecting you if you could get in trouble with dependent base classes, and also "Immediate" lookup that frees you from typename and template.

Unknown specializations

In the code of D, the name typename D::questionable_type is not a member of the current instantiation. Instead the language marks it as a member of an unknown specialization. In particular, this is always the case when you are doing DependentTypeName::Foo or DependentTypedName->Foo and either the dependent type is not the current instantiation (in which case the compiler can give up and say "we will look later what Foo is) or it is the current instantiation and the name was not found in it or its non-dependent base classes and there are also dependent base classes.

Imagine what happens if we had a member function h within the above defined A class template

void h() {
  typename A<T>::questionable_type x;
}

In C++03, the language allowed to catch this error because there could never be a valid way to instantiate A<T>::h (whatever argument you give to T). In C++11, the language now has a further check to give more reason for compilers to implement this rule. Since A has no dependent base classes, and A declares no member questionable_type, the name A<T>::questionable_type is neither a member of the current instantiation nor a member of an unknown specialization. In that case, there should be no way that that code could validly compile at instantiation time, so the language forbids a name where the qualifier is the current instantiation to be neither a member of an unknown specialization nor a member of the current instantiation (however, this violation is still not required to be diagnosed).

Examples and trivia

You can try this knowledge on this answer and see whether the above definitions make sense for you on a real-world example (they are repeated slightly less detailed in that answer).

The C++11 rules make the following valid C++03 code ill-formed (which was not intended by the C++ committee, but will probably not be fixed)

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(); 
}

This valid C++03 code would bind this->f to A::f at instantiation time and everything is fine. C++11 however immediately binds it to B::f and requires a double-check when instantiating, checking whether the lookup still matches. However when instantiating C<A>::g, the Dominance Rule applies and lookup will find A::f instead.

情绪操控生活 2024-07-21 07:52:17
前言
这篇文章是litb 的帖子

基本目的是相同的; 对“什么时候?”的解释 和“为什么?” 必须应用typenametemplate

typenametemplate 的用途是什么?

typenametemplate 可在声明模板以外的情况下使用。

在 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

如果没有任何说明,从属名称将被视为变量或函数。


如何处理从属名称?

如果这是一部好莱坞电影,附属名称将是一种通过身体接触传播的疾病,立即影响其宿主,使其变得混乱。 混乱可能会导致一个不正确的个人、呃……程序。

从属名称是直接或间接依赖于模板参数任何名称。

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
    • “type” 取决于 SomeTrait 的实例化,其中包括 T 和;
  • F
    • “NestedTrait”,这是一个 template-id,取决于 SomeTrait,并且;
    • (F) 末尾的

    • “type” 取决于 NestedTrait,而后者又取决于 SomeTrait,和;


  • G
    • “data”,看起来像一个成员函数模板,间接是一个依赖名称,因为的类型foo 取决于 SomeTrait 的实例化。

如果编译器解释从属名称<,则语句 (E)、(F) 或 (G) 均无效/em> 作为变量/函数(如前所述,如果我们不明确说明的话,就会发生这种情况)。

解决方案

为了使 g_tmpl 具有有效的定义,我们必须显式告诉编译器我们需要 (E )、(F) 中的 template-idtype,以及 (< em>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
}

每次名称表示一个类型时,涉及的所有名称必须是类型名称命名空间,考虑到这一点,很容易看出我们在完全限定名称的开头应用了typename

然而,template 在这方面有所不同,因为无法得出诸如以下的结论: “哦,这是一个模板,那么其他东西也一定是一个模板”。 这意味着我们直接在我们想要处理的任何名称前面应用模板


我可以将关键字放在任何名称前面吗?

我可以将 typenametemplate 放在任何名称前面吗?我不想担心它们出现的上下文。 .” - 一些 C++ 开发人员

标准中的规则规定,只要您处理的是限定名称K< /em>),但如果名称不符合限定,则应用程序格式错误 (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

注意:在不需要的上下文中应用typenametemplate不被认为是好的做法; 仅仅因为您可以做某事,并不意味着您应该这样做。

此外,在某些情况下,typenametemplate明确不允许:

  • 指定类继承的基时

    派生类的base-specifier-list中写入的每个名称都已被视为类型名称,显式指定typename既是格式错误且多余。

     // .-------- 基本说明符列表 
        模板<类T>   // v 
        struct Derived : typename SomeTrait::type /* <- 格式错误 */ { 
          ... 
        }; 
      

  • template-id是在派生类的using-directive中引用的时候

     结构基{ 
          模板<类T> 
          结构体类型{}; 
        }; 
    
        结构派生:基{ 
          使用 Base::template 类型;   // 格式错误 
          使用 Base::type;   // 合法的 
        }; 
      
Preface
This post is meant to be an easy-to-read alternative to litb's post.

The underlying purpose is the same; an explanation to "When?" and "Why?" typename and template must be applied.

What is the purpose of typename and template?

typename and template are usable in circumstances other than when declaring a template.

There are certain contexts in C++ where the compiler must explicitly be told how to treat a name, and all these contexts have one thing in common; they depend on at least one template-parameter.

We refer to such names, where there can be an ambiguity in interpretation, as; "dependent names".

This post will offer an explanation to the relationship between dependent-names, and the two keywords.


A snippet says more than 1000 words

Try to explain what is going on in the following function-template, either to yourself, a friend, or perhaps your cat; what is happening in the statement marked (A)?

template<class T> void f_tmpl () { T::foo * x; /* <-- (A) */ }



It might not be as easy as one thinks, more specifically the result of evaluating (A) heavily depends on the definition of the type passed as template-parameter T.

Different Ts can drastically change the semantics involved.

struct X { typedef int       foo;       }; /* (C) --> */ f_tmpl<X> ();
struct Y { static  int const foo = 123; }; /* (D) --> */ f_tmpl<Y> ();


The two different scenarios:

  • If we instantiate the function-template with type X, as in (C), we will have a declaration of a pointer-to int named x, but;

  • if we instantiate the template with type Y, as in (D), (A) would instead consist of an expression that calculates the product of 123 multiplied with some already declared variable x.


The Rationale

The C++ Standard cares about our safety and well-being, at least in this case.

To prevent an implementation from potentially suffering from nasty surprises, the Standard mandates that we sort out the ambiguity of a dependent-name by explicitly stating the intent anywhere we'd like to treat the name as either a type-name, or a template-id.

If nothing is stated, the dependent-name will be considered to be either a variable, or a function.


How to handle dependent names?

If this was a Hollywood film, dependent-names would be the disease that spreads through body contact, instantly affects its host to make it confused. Confusion that could, possibly, lead to an ill-formed perso-, erhm.. program.

A dependent-name is any name that directly, or indirectly, depends on a template-parameter.

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    
}

We have four dependent names in the above snippet:

  • E)
    • "type" depends on the instantiation of SomeTrait<T>, which include T, and;
  • F)
    • "NestedTrait", which is a template-id, depends on SomeTrait<T>, and;
    • "type" at the end of (F) depends on NestedTrait, which depends on SomeTrait<T>, and;
  • G)
    • "data", which looks like a member-function template, is indirectly a dependent-name since the type of foo depends on the instantiation of SomeTrait<T>.

Neither of statement (E), (F) or (G) is valid if the compiler would interpret the dependent-names as variables/functions (which as stated earlier is what happens if we don't explicitly say otherwise).

The solution

To make g_tmpl have a valid definition we must explicitly tell the compiler that we expect a type in (E), a template-id and a type in (F), and a template-id in (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
}

Every time a name denotes a type, all names involved must be either type-names or namespaces, with this in mind it's quite easy to see that we apply typename at the beginning of our fully qualified name.

template however, is different in this regard, since there's no way of coming to a conclusion such as; "oh, this is a template, then this other thing must also be a template". This means that we apply template directly in front of any name that we'd like to treat as such.


Can I just stick the keywords in front of any name?

"Can I just stick typename and template in front of any name? I don't want to worry about the context in which they appear..." - Some C++ Developer

The rules in the Standard states that you may apply the keywords as long as you are dealing with a qualified-name (K), but if the name isn't qualified the application is ill-formed (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

Note: Applying typename or template in a context where it is not required is not considered good practice; just because you can do something, doesn't mean that you should.

Additionally there are contexts where typename and template are explicitly disallowed:

  • When specifying the bases of which a class inherits

    Every name written in a derived class's base-specifier-list is already treated as a type-name, explicitly specifying typename is both ill-formed, and redundant.

                        // .------- the base-specifier-list
      template<class T> // v
      struct Derived      : typename SomeTrait<T>::type /* <- ill-formed */ {
        ...
      };
    

  • When the template-id is the one being referred to in a derived class's using-directive

      struct Base {
        template<class T>
        struct type { };
      };
    
      struct Derived : Base {
        using Base::template type; // ill-formed
        using Base::type;          // legal
      };
    
吐个泡泡 2024-07-21 07:52:17

<子>
这个答案是一个相当简短而甜蜜的答案,用于回答(部分)标题问题。 如果您想要更详细的答案来解释为什么必须将它们放在那里,请访问此处


放置 typename 关键字的一般规则主要是当您使用模板参数并且想要访问嵌套的 typedef 或 using-alias 时,例如示例:

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()该函数将导致错误:

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()

这样编译器将正确解析它,而不是 t.get < int


This answer is meant to be a rather short and sweet one to answer (part of) the titled question. If you want an answer with more detail that explains why you have to put them there, please go here.


The general rule for putting the typename keyword is mostly when you're using a template parameter and you want to access a nested typedef or using-alias, for example:

template<typename T>
struct test {
    using type = T; // no typename required
    using underlying_type = typename T::type // typename required
};

Note that this also applies for meta functions or things that take generic template parameters too. However, if the template parameter provided is an explicit type then you don't have to specify typename, for example:

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;
};

The general rules for adding the template qualifier are mostly similar except they typically involve templated member functions (static or otherwise) of a struct/class that is itself templated, for example:

Given this struct and function:

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
}

Attempting to access t.get<int>() from inside the function will result in an error:

main.cpp:13:11: error: expected primary-expression before 'int'
     t.get<int>();
           ^
main.cpp:13:11: error: expected ';' before 'int'

Thus in this context you would need the template keyword beforehand and call it like so:

t.template get<int>()

That way the compiler will parse this properly rather than t.get < int.

二手情话 2024-07-21 07:52:17
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

typedef typename Tail::inUnion<U> dummy;

However, I'm not sure you're implementation of inUnion is correct. If I understand correctly, this class is not supposed to be instantiated, therefore the "fail" tab will never avtually fails. Maybe it would be better to indicates whether the type is in the union or not with a simple boolean value.

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: Have a look at Boost::Variant

PS2: Have a look at typelists, notably in Andrei Alexandrescu's book: Modern C++ Design

冰火雁神 2024-07-21 07:52:17

C++20 又名 C++2a

提案,C++20 / C++2a进一步放宽了对typename关键字的要求。 特别是,现在可以在所有这些地方省略 typename,从语法上来说,只有类型才是合法的。 因此,如果未知标记必须是类型,C++20 实际上会将其视为类型。 不过,为了向后兼容,仍然可以使用typename

特别是,大多数 usingtypedef 声明现在可以在不使用 typename 的情况下编写。 在方法返回类型(包括尾随返回类型)的声明中、在方法和 lambda 参数的声明中以及在 static_cast 的类型参数中,也可以省略 typename,<代码>const_cast、dynamic_castreinterpret_cast

一个值得注意的例外是,在用户或库定义模板的实例化参数列表中仍然需要 typename:即使该特定参数被声明为一种类型,typename< /code> 关键字仍然是必需的。 因此 static_cast(arg) 在 C++20 中是合法的,但 my_template_class(arg) 是错误的,如果A 是一个依赖范围,my_template_class 需要一个类型。

举几个例子:

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;

C++20 aka C++2a

As outlined in this Proposal, C++20 / C++2a has further relaxed the requirements for the typename keyword. In particular, typename may now be omitted in all those places, where syntactically only a type is legal. So, if an unknown token must be a type, C++20 will actually treat it as a type. For backwards compatibility, typename may still be used, though.

In particular, most using and typedef declarations can now be written without typename. typename can also be omitted in the declaration of method return types (including trailing return types), in the declaration of method and lambda parameters and in the type argument to static_cast, const_cast, dynamic_cast and reinterpret_cast.

One notable exception, where typename is still required, is in the argument list of instantiations of user or library defined templates: Even, if that particular argument was declared to be a type, the typename keyword is still required. So static_cast<A::B>(arg) is legal in C++20, but my_template_class<A::B>(arg) is ill-formed, if A is a dependant scope and my_template_class expects a type.

A few examples:

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;
逆夏时光 2024-07-21 07:52:17

C++20 和 C++23

对于那些感兴趣的人,我将从现代 C++ 的角度提供完整的答案。
C++20 和 C++23 之间仅存在细微差别。

为什么我们需要templatetypename

基本问题是,如果没有一些额外的提示,编译器就无法理解 C++。 形式上,C++ 语法是上下文相关的,您需要提供额外的消歧器。

int x, y;

template <typename T>
void f() {
    T::type * x;
    // if 'type' is a type, then this is declaring 'x' as a pointer to 'T::type'
    // if 'type' is a static data member, this is multiplying 'x' with 'T::type'
    typename T::type* x; // unambiguous

    T::templ<0>> y;
    // if 'templ' is a template, then this is ( (T::templ<0>) > y) )
    // if 'templ' is a static data member, this is ((T::templ < 0) >> y)
    T::template templ<0>> y; // unambiguous
}

如您所见,问题在于 T 可以是任何类型,因此 T::typeT::templ 可以是任何类型:静态数据成员、类型别名、模板、成员函数模板、非模板成员函数等。
这是因为 T::typeT::templ依赖类型,取决于模板参数T

C++ 通过在有疑问时将 T::something 视为数据成员或成员函数来解决这种歧义。 仅当 T 已知时,或者如果您指定templatetypenamesomething被解释为类型或模板。

依赖类型和表达式

总结一下术语:

  • 诸如 T::type 之类的类型是 依赖于模板参数T
  • (x + y) 等表达式是 类型-dependent 如果xy 依赖于模板类型参数。
  • (x + y) 等表达式是 值-dependent 如果 xy 依赖于非类型模板参数。

一般来说,当涉及依赖类型时,typenametemplate就变得必要了。 如果依赖类型是由依赖表达式形成的,例如 decltype(x + y)::type ,也会发生这种情况。

templatetypename 关键字如何工作?

这些关键字有时称为消歧器,并通知编译器您需要类型或模板而不是数据成员/成员函数。

typename 消歧符

typename 前置于 typename-specifier 并适用于需要消歧义的名字。 例如:

typename T::type // interpreted as type
   │        ▲
   └────────┘

typename T::type::type // interpreted as type
   │              ▲
   └──────────────┘

typename T::templ<0>::type // interpreted as non-type
   │        ▲
   └────────┘

typename T::template templ<0>::type // interpreted as a type
   │                           ▲
   └───────────────────────────┘

template 消歧器

templatequalified-id 这使得 < 不被解释为小于运算符,而是 a 的开头模板参数列表

T::templ<0> // '<' is interpreted as less-than

T::template templ<0> // '<0>' is interpreted as template-argument-list

T::template templ<0>::templ<1> // '<1' is interpreted as less than 1

typename T::templ<0> // '<0>' is interpreted as template-argument-list
   │        ▲        // due to type-only context
   └────────┘

如上一个示例所示,typename 可以使 template 变得不必要,因为 templ< 只能解释为 template-argument-list 如果 templ 必须是类型。

template 还有另一种用途(现已弃用),即用于提供模板-模板参数:

template <template <typename> typename TT>
void f();

template <typename T>
void g() { f<T::template templ>(); }

此用途已被弃用,因为 <...> 始终很清楚> 在此上下文中是 template-argument-list ,而 template 毫无意义。

什么是 C++20 和 C++23 松弛?

许多使用 templatetypename 现在已经变得不必要了。 如上所示,typename 有时会使 template 变得不必要。 更一般地说,仅类型上下文

例如:

using T = T::type;         // OK
static_cast<T::type>(...); // OK
std::is_const_v<T::type>;  // OK
void f(T::type t);         // OK

在 C++20 之前,这些和其他用法都是不正确的。 C++23 添加了 templatetypename 之间的交互,从而消除了进一步的使用。
相关提案为:

一般来说,我们会努力消除所有包含 typenametemplate 的地方 并不是真正必要的。
请注意,templatetypename 并不总是消除歧义所必需的,而只是协助解析。 例如:

typename T::a::b::c::d::e::f::g::h::i::j x,

在这里,我们仍然需要 typename 尽管这明确是一个声明。 否则,需要任意前瞻才能知道 a 是类型并且 a:: 有效。

因此,在这种情况下 typename 不太可能成为可选的。

C++20 and C++23

For those interested, I will provide complete answer from a modern C++ perspective.
There are only minor differences between C++20 and C++23.

Why do we need template and typename?

The basic issue is that C++ can't be understood by the compiler without some additional hints. Formally, the C++ grammar is context-sensitive, and you need to provide additional disambiguators.

int x, y;

template <typename T>
void f() {
    T::type * x;
    // if 'type' is a type, then this is declaring 'x' as a pointer to 'T::type'
    // if 'type' is a static data member, this is multiplying 'x' with 'T::type'
    typename T::type* x; // unambiguous

    T::templ<0>> y;
    // if 'templ' is a template, then this is ( (T::templ<0>) > y) )
    // if 'templ' is a static data member, this is ((T::templ < 0) >> y)
    T::template templ<0>> y; // unambiguous
}

As you can see, the problem is that T can be any type, and so T::type and T::templ can be anything: static data members, type aliases, templates, member function templates, non-template member functions,etc.
This is because T::type and T::templ are dependent types, depending on the template parameter T.

C++ resolves this ambiguity by treating T::something as a data member or member function when in doubt. Only if T is known, or if you specify template or typename, is something interpreted as a type or template.

Dependent types and expressions

To sum up the terminology:

  • A type such as T::type is dependent on a template parameter T.
  • An expression such as (x + y) is type-dependent if x or y depend on a template type parameter.
  • An expression such as (x + y) is value-dependent if x or y depend on a non-type template parameter.

Generally, typename and template become necessary when dependent types are involved. This can also happen if a dependent type is formed from a dependent expression, such as decltype(x + y)::type.

How do the template and typename keywords work?

These keywords are sometimes called disambiguators, and inform the compiler that you want a type or a template instead of a data member/member function.

The typename disambiguator

typename is prepended to a typename-specifier and applies to the first name that needs disambiguation. For example:

typename T::type // interpreted as type
   │        ▲
   └────────┘

typename T::type::type // interpreted as type
   │              ▲
   └──────────────┘

typename T::templ<0>::type // interpreted as non-type
   │        ▲
   └────────┘

typename T::template templ<0>::type // interpreted as a type
   │                           ▲
   └───────────────────────────┘

The template disambiguator

template acts as "glue" within a qualified-id which makes it so < is not interpreted as the less-than operator, but the beginning of a template-argument-list.

T::templ<0> // '<' is interpreted as less-than

T::template templ<0> // '<0>' is interpreted as template-argument-list

T::template templ<0>::templ<1> // '<1' is interpreted as less than 1

typename T::templ<0> // '<0>' is interpreted as template-argument-list
   │        ▲        // due to type-only context
   └────────┘

As seen in the last example, typename can make template unnecessary because templ< can only be interpreted as a template-argument-list if templ must be a type.

There is another (now deprecated) use of template, namely for providing template-template arguments:

template <template <typename> typename TT>
void f();

template <typename T>
void g() { f<T::template templ>(); }

This use has been deprecated because it is always clear that <...> is a template-argument-list in this context, and template is pointless.

What are the C++20 and C++23 relaxations?

Many uses of template and typename have now become unnecessary. As seen above, typename sometimes makes template unnecessary. More generally, typename and template can be omitted in a type-only context.

For example:

using T = T::type;         // OK
static_cast<T::type>(...); // OK
std::is_const_v<T::type>;  // OK
void f(T::type t);         // OK

These and other uses would have been ill-formed prior to C++20. C++23 has added the interaction between template and typename which eliminates further uses.
The relevant proposals are:

In general, there is an effort to eliminate all the places where typename and template aren't truly necessary.
Note that template and typename are not always necessary for disambiguation, but merely assist in parsing. For example:

typename T::a::b::c::d::e::f::g::h::i::j x,

Here, we still need typename despite this unambiguously being a declaration. Otherwise, it would require arbitrarily look-ahead to know that a is a type and a:: is valid.

Therefore, it is unlikely that typename will ever become optional in this context.

圈圈圆圆圈圈 2024-07-21 07:52:17

依赖名称是依赖于模板参数的名称,我们需要在实际实例化模板类/函数之前指示编译器正确编译模板类/函数。

  • 类型名称-> 告诉编译器依赖名称是一个实际类型

    模板 
      结构体依赖类型 
      { 
        类型名 T::类型 a; 
        使用 Type=typename T::type; 
      }; 
    
      
  • template -> 告诉编译器依赖名称是模板函数/类

    模板  
      结构依赖模板 
      { 
        // 模板函数 
        模板 
        静态无效函数(){} 
    
        // 模板类 
        模板 
        结构类名{}; 
      }; 
    
    
      模板 
      无效 foo() 
      { 
        // 调用依赖模板函数的 3 种方法 
        DependentTemplate::模板 func(); 
        DependentTemplate().template func(); 
        (new DependentTemplate())->模板 func(); 
    
        // 您需要 typename 和 template 来引用依赖的模板类 
        类型名称 DependentTemplate::template ClassName   对象; 
        使用 Type=typename DependentTemplate::template ClassName; 
      } 
      

Dependent name is a name depends on template parameters, we need to instruct compiler in order to compile the template class/function properly before actually instiatiate them.

  • typename -> tell compiler the dependent name is an actual type

    template <class T>
    struct DependentType
    {
      typename T::type a;
      using Type=typename T::type;
    };
    
    
  • template -> tell compiler the dependent name is a template function/class

    template <class T>
    struct DependentTemplate
    {
      // template function
      template <class U>
      static void func() {}
    
      // template class
      template <class U>
      struct ClassName{};
    };
    
    
    template <class T1, class T2>
    void foo()
    {
      // 3 ways to call a dependent template function
      DependentTemplate<T1>::template func<T2>();
      DependentTemplate<T1>().template func<T2>();
      (new DependentTemplate<T1>())->template func<T2>();
    
      // You need both typename and template to reference a dependent template class
      typename DependentTemplate<T1>::template ClassName<T2> obj;
      using Type=typename DependentTemplate<T1>::template ClassName<T2>;
    }
    
原来分手还会想你 2024-07-21 07:52:17

我将 JLBorges 的出色对来自 cplusplus.com 的类似问题逐字回复 ,因为这是我读过的关于该主题的最简洁的解释。

在我们编写的模板中,可以使用两种名称 - 依赖名称和非依赖名称。 从属名称是依赖于模板参数的名称; 无论模板参数是什么,非依赖名称都具有相同的含义。

例如:

模板<   类型名称 T >   void foo( T& x, std::string str, int count ) 
  { 
      // 在第二阶段查找这些名称 
      // 当 foo 被实例化并且类型 T 已知时 
      x.size();   // 依赖名称(非类型) 
      T::实例计数;   // 依赖名称(非类型) 
      类型名 T::迭代器 i ;   // 依赖名称(类型) 
      
      // 在第一阶段,  
      // T::instance_count 被视为非类型(这是默认值) 
      // typename 关键字指定 T::iterator 将被视为类型。 

      // 在第一阶段查找这些名称 
      std::string::size_type s ;   // 非依赖名称(类型) 
      std::string::npos ;   // 非依赖名称(非类型) 
      str.empty() ;   // 非依赖名称(非类型) 
      数数 ;   // 非依赖名称(非类型) 
  } 
  

对于模板的每个不同实例化,依赖名称所指的内容可能有所不同。 因此,C++ 模板受到“两阶段名称查找”的影响。 当模板最初被解析时(在任何实例化发生之前),编译器会查找非依赖名称。 当模板的特定实例化发生时,模板参数此时就已知,并且编译器会查找依赖名称。

在第一阶段,解析器需要知道依赖名称是类型名称还是非类型名称。 默认情况下,从属名称被假定为非类型的名称。 依赖名称之前的 typename 关键字指定它是类型的名称。


摘要

仅在模板声明和定义中使用关键字 typename,前提是您有引用类型并依赖于模板参数的限定名称。

I am placing JLBorges's excellent response to a similar question verbatim from cplusplus.com, as it is the most succinct explanation I've read on the subject.

In a template that we write, there are two kinds of names that could be used - dependant names and non- dependant names. A dependant name is a name that depends on a template parameter; a non-dependant name has the same meaning irrespective of what the template parameters are.

For example:

template< typename T > void foo( T& x, std::string str, int count )
{
    // these names are looked up during the second phase
    // when foo is instantiated and the type T is known
    x.size(); // dependant name (non-type)
    T::instance_count ; // dependant name (non-type)
    typename T::iterator i ; // dependant name (type)
      
    // during the first phase, 
    // T::instance_count is treated as a non-type (this is the default)
    // the typename keyword specifies that T::iterator is to be treated as a type.

    // these names are looked up during the first phase
    std::string::size_type s ; // non-dependant name (type)
    std::string::npos ; // non-dependant name (non-type)
    str.empty() ; // non-dependant name (non-type)
    count ; // non-dependant name (non-type)
}

What a dependant name refers to could be something different for each different instantiation of the template. As a consequence, C++ templates are subject to "two-phase name lookup". When a template is initially parsed (before any instantiation takes place) the compiler looks up the non-dependent names. When a particular instantiation of the template takes place, the template parameters are known by then, and the compiler looks up dependent names.

During the first phase, the parser needs to know if a dependant name is the name of a type or the name of a non-type. By default, a dependant name is assumed to be the name of a non-type. The typename keyword before a dependant name specifies that it is the name of a type.


Summary

Use the keyword typename only in template declarations and definitions provided you have a qualified name that refers to a type and depends on a template parameter.

谈情不如逗狗 2024-07-21 07:52:17

Simple

You need it when you call a templated function from inside a templated class:

LiveDemo

#include <iostream>
#include <string>

struct printable {
    std::string mystr = "Hello World";
    template <typename T>
    auto print() {
        if constexpr (std::same_as<T, std::string>) {
            std::cout << mystr << std::endl;
        }
    }
};


template <typename Printable>
struct entity {
    auto print(Printable& myprintable) {
        myprintable.template print<std::string>();
    }
};

int main() {

    entity<printable> e;
    printable p;

    e.print(p);
}

Will output

Hello World

from the templated print() function在可打印中。

Simple

You need it when you call a templated function from inside a templated class:

LiveDemo

#include <iostream>
#include <string>

struct printable {
    std::string mystr = "Hello World";
    template <typename T>
    auto print() {
        if constexpr (std::same_as<T, std::string>) {
            std::cout << mystr << std::endl;
        }
    }
};


template <typename Printable>
struct entity {
    auto print(Printable& myprintable) {
        myprintable.template print<std::string>();
    }
};

int main() {

    entity<printable> e;
    printable p;

    e.print(p);
}

Will output

Hello World

from the templated print() function in printable.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文