提问人:francesco 提问时间:6/23/2020 最后编辑:francesco 更新时间:6/24/2020 访问量:1915
错误:使用 clang 没有可行的重载,使用 gcc 编译
error: no viable overloading with clang, compiles with gcc
问:
以下程序使用 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();
}
错误是:
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();
显然,在模板函数之前移动零件可以消除 clang 错误。using namespace tools
impl::f()
补充说明这里重要的一点是,这是一个模板函数。如果没有模板参数,代码既不会使用 gcc 编译,也不会使用 clang 编译。f
这里什么编译器是正确的?海湾合作委员会还是叮当声?
答:
根据这一点,似乎 clang 就在这里。简而言之 - 您正在扩展您的命名空间,但应该仅向前“传播”到此扩展。using namespace
using 指令指定指定命名空间中的名称可以在 using 指令出现在 using 指令之后的作用域中使用。 在非限定名称查找 ([basic.lookup.unqual]) 期间,名称看起来就像在最近包含 using 指令和指定命名空间的封闭命名空间中声明一样。 [注:在此上下文中,“包含”是指“直接或间接包含”。 — 尾注 ]
评论
U=int
Clang 是对的:非限定的依赖名称查找仅考虑在模板定义点可见的声明
在您的示例中,是函数模板中的依赖名称,在这种情况下,调用的非限定名称查找仅考虑在函数模板的定义点可见的声明。由于命名空间仅作为指定命名空间添加到定义点 之后,因此 unqual.名称查找将看不到从 引入的声明,并且无法看到 。因此,Clang 在这里是正确的,而 GCC 和 MSVC 在不拒绝代码方面是错误的。operator+=
f
a += b;
f
tools
impl
f
tools
tools::operator+=
GCC 的这种行为似乎只在依赖名称引用运算符函数时才存在,而如果我们用命名函数替换运算符,GCC 也会拒绝代码。
被 Clang 拒绝,被 GCC 接受:
struct Dummy{};
namespace ns_g {
template <typename T>
bool operator!(T) { return true; }
} // namespace ns_f
namespace ns_f {
template <typename T>
void f() {
(void)(!T{});
}
// Add ns_g as a nominated namespace to ns_f
// _after_ point of definition of ns_f::f.
using namespace ns_g;
} // namespace ns_f
int main() {
ns_f::f<Dummy>();
return 0;
}
被 Clang 和 GCC 拒绝:
struct Dummy{};
namespace ns_g {
template <typename T>
bool g(T) { return true; }
} // namespace ns_f
namespace ns_f {
template <typename T>
void f() {
(void)(g(T{}));
}
// Add ns_g as a nominated namespace to ns_f
// _after_ point of definition of ns_f::f.
using namespace ns_g;
} // namespace ns_f
int main() {
ns_f::f<Dummy>();
return 0;
}
对于后者,GCC 甚至给了我们一个注释:
注意:''宣布 在这里,稍后在翻译单元中。
template<class T> bool ns_g::g(T)
这种不一致本身就暗示了 GCC 在前一个示例中是错误的,我们可能不会说 Clang 的语言兼容性页面明确提到某些版本的 GCC 可能接受无效代码:
语言兼容性
[...]
模板中的不合格查找
某些版本的 GCC 接受以下无效代码:[...]
即使 Clang 指出的特定示例也被最近的 GCC 版本拒绝,这个问题的上下文是相同的。
GCC 上的未解决错误报告
请注意,一个类似的 SO 问题的 OP(和回答者)(在所有答案都落在这个问题上很久之后我发现),这个问题可能是重复的:
提交了一份关于 GCC 的错误报告,但尚未被认领/解决:
(以下所有 ISO 标准参考均参考 N4659:2017 年 3 月后 Kona 工作草案/C++17 DIS)
标准参考
即使 [temp.res]/9 声明 [extract, emphasis mine]:
[温度]/9查找模板中使用的名称的声明时 definition,通常的查找规则([basic.lookup.unqual], [basic.lookup.argdep]) 用于非依赖名称。查找 依赖于模板参数的名称被推迟到 实际的模板参数是已知的 ([temp.dep])。[ 示例: ... ] [...]
[temp.dep.res]/1 很清楚,只有在模板定义点可见的声明才会被考虑用于非限定(依赖)名称查找 [强调我的]:
[temp.dep.res]/1在解析依赖名称时,来自以下来源的名称包括 考虑:
- (1.1) 在模板定义点可见的声明。
- (1.2) 来自与函数参数类型相关的命名空间的声明,两者都来自实例化上下文 ([temp.point]) 并从定义上下文中。
在 [temp.dep.candidate]/1 [强调我的]中重复的事实:
[临时候选人]/1对于后缀表达式是依赖名称的函数调用, 候选函数是使用通常的查找规则找到的 ([basic.lookup.unqual], [basic.lookup.argdep]) 除了:
- (1.1) 对于使用非限定名称查找的查找部分,只能找到模板定义上下文中的函数声明。
- (1.2) 对于使用关联命名空间 ([basic.lookup.argdep]) 的查找部分,仅在 模板定义上下文或模板实例化上下文 被找到。
其中使用措辞模板定义上下文而不是模板的定义点,但这些是等效的。
根据 [namespace.udir]/2 [强调我的]:
using 指令指定指定命名空间中的名称可以在 using 指令出现在 using 指令之后的作用域中使用。在非限定名称查找期间,名称看起来就像在最近的封闭命名空间中声明一样,该命名空间同时包含 using 指令和指定的命名空间。[注:在此上下文中,“包含”是指“直接或间接包含”。
将 using 指令放在函数模板的定义点之后,相当于简单地在同一定义点之后声明一个名称,并且正如预期的那样,以下修改后的示例被 Clang 拒绝,但被 GCC 接受:f
struct Dummy{};
namespace ns_f {
template <typename T>
void f() {
(void)(!T{});
}
template <typename T>
bool operator!(T) { return true; }
} // namespace ns_f
int main() {
ns_f::f<Dummy>();
return 0;
}
最后,请注意,上面 [temp.dep.candidate]/1 中的 ADL (1.2) 在这里不适用,因为 ADL 不会继续封闭作用域。
非依赖结构可以在没有实例化的情况下被诊断
补充说明这里重要的一点是,这是一个模板函数。如果没有模板参数,代码既不能用 gcc 编译,也不能用 clang 编译。
f
如果要制作成一个非模板类,比如说A
struct A { int x; };
然后 [temp.res]/8.3 适用,并且程序格式不正确,无需诊断:
[温度]/8知道哪些名称是类型名称后,可以检查每个模板的语法。如果出现以下情况,则程序格式不正确,无需诊断:
[...]
(8.3) 由于构造不依赖于模板参数,紧随模板定义之后的模板的假设实例化格式不正确,或者
[...]
评论
operator+=
impl::f();
main()
impl
tools
main()
impl
tools
f
operator+=
tools
代码的格式不正确,因为在模板定义上下文中执行了不依赖于参数的非限定名称查找部分。所以 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>
在模板 (1) 的定义中,对运算符 += 的调用是使用 类型的参数执行的。 是依赖类型,因此也是依赖名称。f
A<U>
A<U>
operator +=
[temp.dep.res]/1 描述如何查找:operator +=
对于后缀表达式是依赖名称的函数调用,可以使用模板定义上下文([basic.lookup.unqual], [basic.lookup.argdep]) 中的常用查找规则找到候选函数。[注意:对于使用关联命名空间([basic.lookup.argdep])的查找部分,此查找将找到在模板实例化上下文中找到的函数声明,如[basic.lookup.argdep]中所述。[...]
执行了两个查找。
与参数无关的非限定名称查找 [basic.lookup.unqual]。
此查找是从模板定义上下文执行的。“从模板定义上下文”是指模板定义点的上下文。术语“上下文”是指查找上下文。如果模板首先在命名空间中声明,然后在全局命名空间范围内定义,则非限定名称查找仍将查找命名空间的成员。这就是为什么规则 [temp.dep.res]/1 使用“模板定义上下文”而不是简单的“模板定义点”的原因。f
impl
impl
此查找是从 (1) 执行的,它找不到 在命名空间中定义 。using 指令的出现晚于 (1),并且不起作用。operator +=
tools
参数相关名称查找 (ADL) [basic.lookup.argdep]
ADL 在实例化点 (2) 执行。所以它是在 using 指令之后实现的。尽管如此,ADL 仅考虑与参数类型关联的命名空间。参数具有 type ,模板是全局命名空间的成员,因此 ADL 只能找到此命名空间的成员。A<int>
A
在 (2) 处,全局命名空间作用域中没有声明。因此,ADL 也无法找到 .operator +=
operator +=
评论