我发现当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;
}
Run Code Online (Sandbox Code Playgroud)
我们有一个模板函数foo_negative,它接受任何类型的对象并确定其foo属性是否为负数.该main函数foo_negative用[T = X] 实例化.该程序编译并运行,没有任何输出.
现在,将此函数添加到程序的顶部:
template <class T>
void foo()
{
}
Run Code Online (Sandbox Code Playgroud)
使用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
Run Code Online (Sandbox Code Playgroud)
(第13行是return v.foo < 0第25行assert(!foo_negative(x)).)
Clang产生类似的错误.
笏?如何添加一个从未调用过的无关函数来管理将语法错误引入有效程序?在解析时foo_negative,编译器不知道其类型v,并且至关重要的是,它不知道v.foo是成员模板还是常规成员.显然,它必须决定解析时间(在模板实例化之前)是将其视为成员模板还是常规成员.
如果它认为v.foo是一个成员模板,那么< 0被视为传递0为模板参数,并且缺少>,因此语法错误.然后,当foo_negative用[T = X]实例化时,还有另一个错误,因为X::foo它不是成员模板.
但为什么它认为v.foo是会员模板?这种歧义恰恰是template关键字的用途:如果我写了v.template foo,那么我会明确告诉C++期望一个成员模板,但我没有使用该template关键字!我没有引用成员模板,所以它应该假设它是一个普通的成员.事实上,与成员具有相同名称的功能不应该产生任何影响.为什么呢?它不能是编译器中的错误,因为GCC和clang是一致的.
它看起来像编译器错误.
标准说:
名称查找(3.4)后发现名称是模板名称或者operator-function-id或literal-operator-id引用一组重载函数,如果后面跟着函数模板,则其中任何成员都是函数模板a <,<始终作为模板参数列表的分隔符,永远不作为小于运算符.
在3.4.5/1中
在类成员访问表达式(5.2.5)中,如果是.或 - > token后面紧跟一个标识符后跟一个<,必须查找标识符以确定<是模板参数列表(14.2)的开头还是小于运算符.首先在对象表达式的类中查找标识符.如果未找到标识符,则在整个postfix-expression的上下文中查找它,并命名一个类模板.
该标准似乎并未表明名称查找可以在此处找到非成员函数模板.在任何情况下,<应该在模板定义时决定意义,而不是实例化时间(那时为时已晚).
这是一个错误。
您的代码在 MSVC (2011) 上运行良好。我认为编译器的解析器将“<”翻译为模板语句的起始标记。但是为什么Clang和GCC同时出现这个bug呢?
也许这也很有趣: Another bug in g++/Clang? [C++ 模板很有趣]
在 Clang 中,这是PR11856,大约 2.5 个月前已修复。Clang trunk 和 Clang 3.1 不会报告此代码的任何错误。Clang bug 包括解释为什么此代码被拒绝以及为什么代码是正确的,在此处复制(稍微调整以解决您的情况):
这里重要的段落是[basic.lookup.classref]p1:
“在类成员访问表达式 (5.2.5) 中,如果
.or->标记后紧跟一个标识符,后跟一个<,则必须查找该标识符以确定是否<是模板参数列表 (14.2) 的开头或小于运算符。首先在对象表达式的类中查找标识符。如果找不到标识符,则在整个后缀表达式的上下文中查找它,并命名一个类模板。”由于
v是依赖的,因此可能找不到 标识符,因此我们考虑如果我们在整个后缀表达式的上下文中查看会发生什么。既然我们找到了一个函数模板,我们不应该得出这样的结论:我们有template-id的开头。