20

我发现当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 属性是否为负。该函数使用 [T = X] 进行main实例化。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模板参数传递,并且缺少>,因此出现语法错误。然后,当foo_negative用 [T = X] 实例化时,还有另一个错误,因为X::foo不是成员模板。

但是为什么它认为v.foo是成员模板呢?这种歧义正是template关键字的用途:如果我写了v.template foo,那么我会明确告诉 C++ 期待一个成员模板,但我没有使用template关键字!我没有提到成员模板,所以它应该假设它是一个普通成员。有一个与成员同名的函数这一事实不应该有任何影响。为什么会这样?它不可能是编译器中的错误,因为 GCC 和 clang 是一致的。

4

3 回答 3

6

它看起来确实像一个编译器错误。

标准说:

在名称查找 (3.4) 发现名称是模板名称或 operator-function-id 或literal-operator-id 引用一组重载函数后,其中任何成员都是函数模板,如果后面跟着a <,< 总是作为模板参数列表的分隔符,而不是小于运算符。

并在 3.4.5/1

在类成员访问表达式 (5.2.5) 中,如果 . 或 -> 标记后紧跟一个标识符,后跟一个 <,必须查找标识符以确定 < 是模板参数列表 (14.2) 的开头还是小于运算符。标识符首先在对象表达式的类中查找。如果未找到标识符,则在整个后缀表达式的上下文中查找它并命名一个类模板。

该标准似乎没有表明名称查找可以在这里找到非成员函数模板。无论如何,<应该在模板定义时确定 的含义,而不是实例化时(那时为时已晚)。

于 2012-05-20T20:00:55.800 回答
5

这是一个错误。

您的代码在 MSVC (2011) 上运行良好。我认为编译器的解析器将“<”翻译为模板语句的开始标记。但是为什么 Clang 和 GCC 同时有这个 bug?

请在此处此处报告错误。

也许这也很有趣: g++/Clang 中的另一个错误?[C++ 模板很有趣]

于 2012-05-20T10:52:38.480 回答
3

在 Clang 中,这是PR11856,大约在 2.5 个月前修复。Clang trunk 和 Clang 3.1 不报告此代码的任何错误。Clang 错误包括解释为什么此代码被拒绝,以及为什么代码正确,在此处复制(稍微调整以解决您的情况):

这里重要的段落是[basic.lookup.classref]p1

“在类成员访问表达式 (5.2.5) 中,如果.or->标记后紧跟一个标识符,后跟 a <,则必须查找标识符以确定<是模板参数列表 (14.2) 的开头还是 a小于运算符。首先在对象表达式的类中查找标识符。如果找不到标识符,则在整个后缀表达式的上下文中查找它,并命名一个类模板。

由于v是依赖的,因此可能找不到标识符,因此我们考虑如果我们查看整个 postfix-expression的上下文会发生什么。由于我们找到了一个函数模板,我们不应该断定我们有一个template-id的开头。

于 2012-05-29T20:44:23.980 回答