【问题标题】:Template dependent typename模板依赖类型名
【发布时间】:2016-01-09 17:03:31
【问题描述】:

考虑以下代码:

struct bar
{
  template <typename U>
  void fun0() const {}
};

template <typename T>
struct foo
{
  void
  fun1(const bar& d)
  {
    // (1) KO
    fun2(d).fun0<int>();
    // (2) OK
    fun2(d).template fun0<int>();
    // (3) OK        
    d.fun0<int>();
  }

  bar
  fun2(const bar& d)
  {
    return d;
  }
};

第 (2) 和 (3) 行编译,但 (1) 失败:

error: use 'template' keyword to treat 'fun0' as a dependent template name
    fun2(d).fun0<int>();

            ^
            template 

(正如预期的那样,如果foo 不再是模板结构,(1)也会编译)

这里为什么bar::fun0 是一个依赖模板名? bar 不依赖于foo 的模板参数T

编辑:

显然,bar::fun2 负责 .template 处理的歧义。例如,让我们添加以下 2 个免费函数:

bar
fun3(const bar& d)
{
  return d;
}

template <typename T>
T
fun4(const T& d)
{
  return d;
}

fun3(d).fun0&lt;int&gt;()fun4(d).fun0&lt;int&gt;()) 也在foo::fun1 的上下文中编译。所以歧义是由foo的模板参数引起的。

为什么fun2(d).fun0&lt;int&gt;() 不被解析为对成员函数模板的调用?

【问题讨论】:

  • 因为您可能会专门化 fun2 以返回除 bar 以外的其他内容。
  • @AlanStokes 您将如何专门化 fun2 以允许更改返回类型,但仍保留 fun1
  • @AlanStokes 你只能专门化fun2 来返回一个协变类型(所以它必须是从bar 派生的),所以保证协变类型具有相同的功能(即它必须具有template &lt;typename&gt; void fun0() const 函数)。所以它总是会返回一个实际上是 bar 的类型
  • @AlanStokes 你不能返回bazfun2 已声明为返回 bar。您需要专门化整个模板,而不仅仅是成员,以允许返回类型不同,并且在专门化整个模板时,fun1 不会被保留。
  • 这适用于虚函数的覆盖,这里不相关。

标签: c++ templates language-lawyer


【解决方案1】:

我认为这归结为标准14.6.2 部分中的规则。基本上,我认为规则在要求您使用templatetypename 时会犯错误——一旦你形成一个可能依赖的表达式,有时它会根据规则声明,即使人类可以清楚地看到它实际上并不依赖。然后,有一个通用规则14.6.2.2.1 声明

除了下面描述的,如果任何子表达式是类型相关的,则表达式是类型相关的。

我认为正在发生的事情是表达式 fun2(d) 被规则声明为依赖于类型,即使该类型实际上在 this(主要)的每个实例中都是相同的模板,正如你和我所看到的——这就是为什么template 是必需的。

在C++11(或C++14)标准14.6.2.1.4下,

如果名称是当前实例化的成员,则它是当前实例化的依赖成员 查找时,它指的是当前实例化的类的至少一个成员。

这意味着,名称fun2 是一个依赖名称,即使fun2 不引用T 或任何明确依赖于T 的东西。

因此当我们考虑你给fun2(d).fun0&lt;int&gt;()的表达式时,再考虑“成员访问子表达式”,即fun2(d) . fun0&lt;int&gt;——直觉上我们想说这不是依赖的,因为fun2(d) 始终是bar 类型,而fun0&lt;int&gt; 也不依赖于T。有规则14.6.2.2.5 声明

类成员访问表达式 (5.2.5) 如果表达式引用当前的成员,则依赖于类型。 实例化和被引用成员的类型依赖,或者类成员访问表达式 指代未知专业的成员。

这些条件都不适用,因为bar 与当前实例化 (foo&lt;T&gt;) 的类型不同,fun0&lt;int&gt; 也不是 foo 模板的未知特化的成员。这就是表达式 d.fun0&lt;int&gt;() 格式正确的原因。

但是,请清楚注意,这条规则 14.6.2.2.5 位于 14.6.2.2.1 之后,它设置了整个部分的含义:

除了下面描述的,如果任何子表达式是类型相关的,则表达式是类型相关的。

因此,如果任何子表达式,例如fun2,在形式上是“依赖于类型”的,它会毒化整个表达式,并导致应用类似的规则,就好像我们正在查找模板参数的成员或未知的特化等一样。等等

具体来说,type-dependent 条件意味着您需要 template 前缀,因为规则 14.2.4:

当成员模板特化的名称出现在后缀表达式中的 .-&gt; 之后或之后 限定 id 中的嵌套名称说明符,后缀表达式的对象表达式是类型相关的 或qualified-id 中的nested-name-specifier 指的是依赖类型,但该名称不是 当前实例化(14.6.2.1),成员模板名称必须以关键字模板为前缀。 否则,该名称被假定为命名非模板。

例子:

struct X {
  template<std::size_t> X* alloc();
  template<std::size_t> static X* adjust();
};
template<class T> void f(T* p) {
  T* p1 = p->alloc<200>();          // ill-formed: < means less than
  T* p2 = p->template alloc<200>(); // OK: < starts template argument list
  T::adjust<100>();                 // ill-formed: < means less than
  T::template adjust<100>();        // OK: < starts template argument list
}  

—结束示例]

现在非常具体:

  1. 通过[14.2.4],在后缀表达式fun2(d).fun0&lt;int&gt;中,如果对象表达式fun2(d)类型相关的,那么你必须使用template前缀调用成员模板。

  2. 在 [14.6.2.2.1] 下,如果 fun2 依赖于类型,则强制 fun2(d) 也是。

  3. 在 [14.6.2.1.4] 下,由于 fun2 在查找时指的是 foo 类模板的成员,仅此一项就足以使其成为当前实例化。

我没有从任何规则中得到一个完全明确的论据,即如果名称引用当前实例化的一个依赖成员,那么这意味着与该名称对应的表达式是@ 987654364@...我已经搜索过几次了。

但是,@Johannes Schaub - litb 的highly up-voted (but simplified, and non-normative) exposition of the rules 提倡这种观点:

从属名称

标准有点不清楚究竟什么是从属名称。在简单的阅读中(你知道,最不意外的原则),它定义为依赖名称的所有内容都是下面函数名称的特例。但是由于显然 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)

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

如果能对最后一部分进行更具体的解释,那就太好了。

【讨论】:

  • 感谢您的回答。所以看起来这一切都归结为 C++ 标准中依赖名称的“过于宽泛”的定义?
  • 当然,这是看待它的一种方式。它是否是“缺陷”是一个单独的问题——事实是 C++ 语法是模棱两可的,编写一个高性能且正确的解析器是困难的。在这种情况下要求template 可能允许编写更快且使用更少内存的解析器。后来编译器知道fun2(d) 的类型,但在解析器阶段,它只是想弄清楚fun0&lt;int&gt; 中的&lt; 是比较运算符还是模板参数列表的开头——当时templatetypename 是相关的,它对您的程序几乎一无所知。
  • 降低解析器的压力固然很好,但增加了程序员的压力,他们必须思考并弄清楚为什么在这种情况下必须输入template,毕竟这可能是净负面的。好吧,也许在这种情况下,如果只有所有编译器都能提供像 clang 这样的漂亮错误消息,那就没关系了。
猜你喜欢
  • 2014-12-31
  • 2013-03-26
  • 2017-09-08
  • 2011-02-25
  • 2011-04-19
  • 1970-01-01
  • 1970-01-01
  • 2013-03-03
相关资源
最近更新 更多