C++混淆了成员模板的属性名称

C++ confusing attribute name for member template

本文关键字:属性 成员 C++      更新时间:2023-10-16

我发现,当从模板类型(T& v)的变量访问非模板属性(v.foo)时,如果存在同名的模板函数(template class <T> void foo()),C++可能会被欺骗,以为它是成员模板。如何从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是一致的。

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

标准上写着:

在名称查找(3.4)发现一个名称是模板名称或运算符函数id或文字运算符id指的是重载函数,其中任何成员都是函数模板,如果这之后是一个<,<总是作为的分隔符模板参数列表,并且从不作为小于运算符。

和3.4.5/1

在类成员访问表达式(5.2.5)中,如果。或->令牌为紧随其后的是标识符,后跟<,标识符必须查找以确定<是一个模板参数列表(14.2)或小于运算符。标识符首先在对象表达式的类中查找。如果找不到标识符,然后在整个后缀表达式,并应命名一个类模板。

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

这是一个错误

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

请在此处和此处报告错误。

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

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

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

"在类成员访问表达式(5.2.5)中,如果.->令牌紧随其后的是标识符,然后是<,该标识符必须查找以确定<是否是模板参数列表(14.2)或小于运算符。标识符首先在对象表达式的类中查找。如果找不到标识符,然后在整个后缀表达式,并应命名一个类模板。">

由于v是依赖的,因此可能找不到标识符,因此我们考虑一下如果我们从整体的角度来看会发生什么后缀表达式。既然我们找到了一个函数模板,我们就不应该得出结论,我们有一个模板id的开始。