模板相关的typename

Ale*_*mez 15 c++ templates language-lawyer

请考虑以下代码:

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;
  }
};
Run Code Online (Sandbox Code Playgroud)

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

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

            ^
            template 
Run Code Online (Sandbox Code Playgroud)

(正如所料,如果foo不再是模板结构,(1)也编译)

为什么bar::fun0依赖模板名称在这里?bar不依赖于模板参数Tfoo.

编辑:

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

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

template <typename T>
T
fun4(const T& d)
{
  return d;
}
Run Code Online (Sandbox Code Playgroud)

fun3(d).fun0<int>()并且fun4(d).fun0<int>()还在上下文中编译foo::fun1.因此模糊性是由模板参数引起的foo.

为什么fun2(d).fun0<int>()不将其解析为对成员函数模板的调用?

Chr*_*eck 5

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

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

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

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

如果名称是当前实例化的成员,并且在查找时指的是作为当前实例化的类的至少一个成员,则该名称是当前实例化从属成员

这意味着,该名称fun2是一个从属名称,即使fun2不引用T或任何明确依赖于T.

因此,当我们考虑您给出的表达式fun2(d).fun0<int>()并考虑“成员访问子表达式”时,也就是说,-- 直观地,我们想说 this 不依赖,因为它总是类型并且不依赖任何一个。有规定fun2(d) . fun0<int>fun2(d)barfun0<int>T14.6.2.2.5

类成员访问表达式 (5.2.5) 是类型相关的,如果表达式引用当前实例化的成员并且引用成员的类型是相关的,或者类成员访问表达式引用未知特化的成员。

这两个条件都不是字面意义,因为bar它与当前实例化 ( foo<T>) 的类型不同,也不是模板fun0<int>的未知特化的成员foo。这就是该表达式d.fun0<int>()格式良好的原因。

但是,请清楚地注意,此规则14.6.2.2.5之后14.6.2.2.1设置了整个部分的含义:

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

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

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

当成员模板特化的名称出现在后缀表达式之后.->中,或在限定 id 中的嵌套名称说明符之后,并且后缀表达式的对象表达式是类型相关的或嵌套名称说明符时在qualified-id 指的是依赖类型,但名称不是当前实例化的成员(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
}  
Run Code Online (Sandbox Code Playgroud)

— 结束示例 ]

现在非常具体:

  1. 通过[14.2.4],在后缀表达式中fun2(d) . fun0<int>,如果对象表达式fun2(d)类型依赖的,那么你必须使用template前缀来调用成员模板。

  2. 在 [14.6.2.2.1] 下,如果fun2类型相关的,那么它fun2(d)也必须是。

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

从任何规则中我都没有一个完全明确的论点,即如果名称引用当前实例化依赖成员,那么这意味着与名称对应的表达式是type-dependent......我现在已经搜索了几次.

然而,@Johannes Schaub 提倡这种观点 - litb 对规则的高度赞成(但简化和非规范)阐述

从属名称

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

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

名称是使用标识符 (2.11)、运算符功能 ID (13.5)、转换功能 ID (12.3.2) 或模板 ID (14.2) 来表示实体或标签 (6.6.4, 6.1)

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

对最后一部分有更具体的解释会很棒。