fra*_*sco 16 c++ templates namespaces language-lawyer
下面的程序可以用 g++(版本 10.1.0)编译,但不能用 clang++(10.0.0)编译
#include <iostream>
template <typename U>
struct A { U x; };
namespace tools {
template <typename U>
void operator+=(A<U>& lhs, const A<U>& rhs) { lhs.x += rhs.x; }
}
namespace impl {
template <typename U = int>
void f() {
A<U> a{3};
A<U> b{2};
a += b;
std::cout << a.x << std::endl;
}
}
namespace impl {
using namespace tools;
}
int main()
{
impl::f();
}
Run Code Online (Sandbox Code Playgroud)
错误是:
name.cpp:16:7: error: no viable overloaded '+='
a += b;
~ ^ ~
name.cpp:27:9: note: in instantiation of function template specialization 'impl::f<int>' requested here
impl::f();
Run Code Online (Sandbox Code Playgroud)
显然,using namespace tools在模板函数之前移动部分impl::f()消除了 clang 的错误。
附加说明
这里的一个重点是它f是一个模板函数。如果没有模板参数,代码既不能用 gcc 编译,也不能用 clang 编译。
什么编译器在这里是正确的?gcc还是clang?
代码格式错误,因为不依赖于参数的非限定名称查找部分是在模板定义上下文中执行的。所以 Clang 是对的,并且已经报告了 GCC 错误(错误 #70099)
接下来是冗长的解释。
在您的示例代码中,有一些地方必须标记,以便讨论:
namespace impl {
template <typename U = int>
void f() { // (1) point of definition of the template f
A<U> a{3};
A<U> b{2};
a += b; // call operator += with arguments of dependent type A<U>
std::cout << a.x << std::endl;
}
}
namespace impl {
using namespace tools; // using directive
}
int main()
{
impl::f();
} // (2) point of instantiation of impl::f<int>
Run Code Online (Sandbox Code Playgroud)
在模板f(1)的定义中,对运算符 += 的调用使用类型为 的参数执行A<U>。A<U>是一个依赖型,所以operator +=是一个从属名称。
[temp.dep.res]/1描述如何operator +=查找:
对于后缀表达式是依赖名称的函数调用,使用模板定义上下文([basic.lookup.unqual]、[basic.lookup.argdep])中的常用查找规则找到候选函数。[注意:对于使用关联命名空间([basic.lookup.argdep])的查找部分,在模板实例化上下文中找到的函数声明通过此查找找到,如[basic.lookup.argdep]中所述。— 尾注][...]
执行了两个查找。
此查找是从模板定义上下文执行的。“来自模板定义上下文”是指模板定义点的上下文。术语“上下文”指的是查找上下文。如果模板f首先在 namespace 中声明,impl然后在全局命名空间范围中定义,则非限定名称查找仍会找到 namespace 的成员impl。这就是规则 [temp.dep.res]/1 使用“模板定义上下文”而不是简单的“模板定义点”的原因。
这个查找是从 (1) 执行的,它没有找到operator +=在 namespace 中定义的tools。using 指令出现在 (1) 之后,并且无效。
ADL 在实例化点 (2) 执行。所以它是在 using 指令之后实现的。尽管如此,ADL 只考虑与参数类型关联的命名空间。参数有 type A<int>,模板A是全局命名空间的成员,所以 ADL 只能找到这个命名空间的成员。
在 (2) 处没有operator +=在全局命名空间范围内声明。因此 ADL 也无法找到operator +=.