请考虑以下代码:
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>()
未被解析为对成员函数模板的调用?
答案 0 :(得分:4)
我认为这取决于标准14.6.2
部分的规则。基本上,我认为规则在要求你使用template
和typename
时要小心谨慎 - 一旦你形成一个可能依赖的表达式,有时它会根据规则,即使人类可以清楚地看到它实际上并不依赖。然后,有一个通用规则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-dependent
或template
之后或之后 qualified-id中的nested-name-specifier,postfix-expression的对象表达式依赖于类型 或者qualified-id中的nested-name-specifier引用依赖类型,但名称不是其成员 在当前实例化(14.6.2.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>
前缀来调用成员模板。
在[14.6.2.2.1]下,如果fun2(d)
依赖于类型,则会强制template
。
在[14.6.2.1.4]下,由于fun2
被查询时指的是fun2(d)
类模板的成员,仅此一项就足以使其成为当前实例化的从属成员。
如果一个名称引用当前实例化的依赖成员,那么我没有从任何规则中得到一个完全明确的论据,那么这意味着该名称对应的表达式是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。所有这些都是名称,并且通过标准中的常规用法,名称还可以包括限定符,用于说明应该查找名称的名称空间或类。
很高兴能够对最后一部分进行更具体的解释。