我发现当从模板类型(v.foo
)的变量访问非模板属性(T& v
)时,可以欺骗C ++认为它是成员模板有一个同名的模板函数(template class <T> void foo()
)。如何从C ++规范中解释这一点?考虑一下这个简单的程序:
#include <cassert>
/** Determine whether the 'foo' attribute of an object is negative. */
template <class T>
bool foo_negative(T& v)
{
return v.foo < 0;
}
struct X
{
int foo;
};
int main()
{
X x;
x.foo = 5;
assert(!foo_negative(x));
return 0;
}
我们有一个模板函数foo_negative
,它接受任何类型的对象并确定其foo属性是否为负数。 main
函数用[T = X]实例化foo_negative
。该程序在没有任何输出的情况下编译和运行。
现在,将此功能添加到程序的顶部:
template <class T>
void foo()
{
}
使用G ++ 4.6.3进行编译会导致此编译器错误:
funcs.cpp: In function ‘bool foo_negative(T&)’:
funcs.cpp:13:14: error: parse error in template argument list
funcs.cpp: In function ‘bool foo_negative(T&) [with T = X]’:
funcs.cpp:25:5: instantiated from here
funcs.cpp:13:14: error: ‘foo’ is not a member template function
(第13行为return v.foo < 0
,第25行为assert(!foo_negative(x))
。)
Clang会产生类似的错误。
笏?如何添加一个从未调用过的无关函数来管理将语法错误引入有效程序?解析foo_negative
时,编译器不知道v
的类型,而且至关重要的是,它不知道v.foo
是成员模板还是常规成员。显然,它必须决定解析时间(在模板实例化之前)是将其视为成员模板还是常规成员。
如果它认为v.foo
是成员模板,则< 0
被视为传递0
作为模板参数,并且缺少>
,因此语法错误。然后,当使用[T = X]实例化foo_negative
时,会出现另一个错误,因为X::foo
不是成员模板。
但为什么认为v.foo
是会员模板?这种歧义恰恰是template
关键字的用途:如果我写了v.template foo
,那么我会明确地告诉C ++期望一个成员模板,但我没有使用template
关键字!我没有引用成员模板,所以它应该假设它是一个普通的成员。与成员同名的功能的事实不应该有任何影响。为什么呢?它不能是编译器中的错误,因为GCC和clang是一致的。
答案 0 :(得分:6)
它看起来像编译器错误。
标准说:
名称查找(3.4)后发现名称是模板名称或名称 operator-function-id或literal-operator-id指的是一组 重载函数的任何成员是函数模板if 接下来是&lt;,&lt;总是被视为a的分隔符 template-argument-list,从不作为less-than运算符。
和3.4.5 / 1
在类成员访问表达式(5.2.5)中,如果是。或 - &gt;令牌是 紧接着是一个标识符,后跟一个&lt ;,标识符 必须抬头确定是否&lt;是一个开始 模板参数列表(14.2)或小于运算符。标识符 首先在对象表达式的类中查找。如果 找不到标识符,然后在上下文中查找 整个postfix-expression并命名一个类模板。
标准似乎并不表示名称查找可以在此处找到非成员函数模板。在任何情况下,<
的含义应该在模板定义时决定,而不是在实例化时间(那时为时已晚)。
答案 1 :(得分:4)
这是一个错误。
您的代码在MSVC(2011)上正常运行。 我认为编译器的解析器翻译了'&lt;'作为模板语句的开始标记。但是为什么Clang和GCC会在同一时间出现这个错误?
答案 2 :(得分:2)
在Clang,这是PR11856,大约在2.5个月前修复。 Clang trunk和Clang 3.1不会报告this code的任何错误。 Clang错误包括explanation为什么代码被拒绝,为什么代码是正确的,在这里复制(稍微调整以解决您的情况):
此处重要的段落是 [basic.lookup.classref] p1 :
“在类成员访问表达式(5.2.5)中,如果
.
或->
令牌是 紧接着是标识符,后跟标识符<
必须查找以确定<
是否是a的开头 模板参数列表(14.2)或小于运算符。标识符 首先在对象表达式的类中查找。如果 找不到标识符,然后在上下文中查找 整个 postfix-expression 并命名一个类模板。“由于
v
是依赖的,因此我们可能找不到标识符 考虑如果我们查看整个环境会发生什么 后缀表达式。由于我们找到了一个函数模板,我们不应该 得出结论,我们已经开始了 template-id 。