模板相关的typename

时间:2016-01-09 17:03:31

标签: 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;
  }
};

第(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不依赖于T的模板参数foo

编辑:

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

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

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

fun3(d).fun0<int>()fun4(d).fun0<int>())也会在foo::fun1的上下文中进行编译。因此,模糊性是由foo的模板参数引起的。

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

1 个答案:

答案 0 :(得分:4)

我认为这取决于标准14.6.2部分的规则。基本上,我认为规则在要求你使用templatetypename时要小心谨慎 - 一旦你形成一个可能依赖的表达式,有时它会根据规则,即使人类可以清楚地看到它实际上并不依赖。然后,有一个通用规则14.6.2.2.1表明

  

除非如下所述,否则如果任何子表达式依赖于类型,则表达式依赖于类型。

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

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

  

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

这意味着,名称fun2依赖名称,即使fun2未引用T或明确依赖{{1}的任何内容}}

因此,当我们考虑您提供的表达式T时,请考虑&#34;成员访问子表达式&#34;,即fun2(d).fun0<int>() fun2(d) . - 直觉上我们想说这不依赖,因为fun0<int>始终是fun2(d)类型而bar并不依赖{{1}也许。有规则fun0<int>表明

  

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

这些条件都不是字面上适用的,因为T与当前实例化(14.6.2.2.5)的类型不同,bar也不是foo<T>的未知专门化成员{1}}模板。这就是表达式fun0<int>格式正确的原因。

但请注意,此规则foo位于d.fun0<int>()之后,它确定了整个部分的含义:

  

除非如下所述,否则如果任何子表达式依赖于类型,则表达式依赖于类型。

因此,如果任何子表达式(例如14.6.2.2.5)正式与#34;类型相关&#34;,它会中毒整个表达式,并导致类似的规则应用,就像我们查找模板的成员一样参数或未知专业化等等。

具体而言,14.6.2.2.1条件意味着您需要fun2前缀,因为规则14.2.4:

  

当成员模板专精的名称出现在后缀表达式中的type-dependenttemplate之后或之后   qualified-id中的nested-name-specifier,postfix-expression的对象表达式依赖于类型   或者qualified-id中的nested-name-specifier引用依赖类型,但名称不是其成员   在当前实例化(14.6.2.1)中,成员模板名称必须以关键字模板为前缀。   否则,假定该名称命名非模板。

     

示例:

.
  

- 结束示例]

现在非常具体:

  1. 通过[14.2.4],在后缀表达式-> 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 } fun2(d)中,如果对象表达式. 类型相关< / em>,然后你必须使用fun0<int>前缀来调用成员模板。

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

  3. 在[14.6.2.1.4]下,由于fun2被查询时指的是fun2(d)类模板的成员,仅此一项就足以使其成为当前实例化的从属成员

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

    然而,@Johannes Schaub提出了这一观点 - litb&#39; highly up-voted (but simplified, and non-normative) exposition of the rules

      

    从属名称

         

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

         

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

         

    名称是标识符(2.11),operator-function-id(13.5),conversion-function-id(12.3.2)或template-id(14.2)的使用,表示实体或标签(6.6) .4,6.1)

         

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

    很高兴能够对最后一部分进行更具体的解释。