以下简化示例以gcc
和Visual Studio
编译,但以clang
失败!
namespace N
{
struct A {};
template <typename T>
double operator+ (T a, double d) {return d;}
template <typename T>
double operator+ (double d, T a) {return d;}
}
void test()
{
N::A a;
double x;
double y = a + x;
double z = x + a;
}
如我所见,命名空间
operator+
中的模板化N
应该由ADL找到。为什么
clang
不同意?这是clang
还是其他编译器中的错误?这是来自clang 3.5.1的编译错误(在coliru上测试),我不明白这里是什么问题...
10 : error: overloaded 'operator+' must have at least one parameter of class or enumeration type
double operator+ (double d, T a) {return d;}
^
18 : note: in instantiation of function template specialization 'N::operator+' requested here
double y = a + x;
^
7 : error: overloaded 'operator+' must have at least one parameter of class or enumeration type
double operator+ (T a, double d) {return d;}
^
19 : note: in instantiation of function template specialization 'N::operator+' requested here
double z = x + a;
^
2 errors generated.
Compilation failed
当然,该示例是从现实生活中的代码简化而来的。目的是在 namespace N中定义的任何类都具有带double的重载operator +。
最佳答案
这是由两个不同的CWG问题引起的:CWG issue 2052和CWG issue 1391。
首先,是CWG1391。在遇到x + a
时,通常的名称查找会发现其他重载,
template <typename T> double operator+ (T, double);
模板参数推导是通过将
T
与+
的lhs类型匹配的,即double
进行的,因此推导T
为double
。第二个参数的类型不包含模板参数,因此在当前规则下不考虑。可以肯定的是,N::A
不能转换为double
,因此产生的特化是不可行的,但是当前的规则说模板参数推导对此并不在意。将以过载解析的方式进行处理。CWG 1391的拟议决议,除其他外,在标准中增加了新段落:
换句话说,如果不能将与非依赖性参数(
a
)对应的参数(在我们的情况下为double
)转换为参数的类型,则推论将简单地失败。因此,在我们的案例中,CWG1391之后的模板参数推导将因这种重载而失败,并且一切都会好起来的。Clang执行当前规则,但是,推导成功完成
T = double
,替换发生,并且我们遇到了CWG2052。引用Richard Smith(Clang开发人员)的文章:这不是SFINAE上下文,所以程序格式不正确,而是
而不是选择内置运算符。
在这种情况下,没有转换,因此推断出的
operator+(double, double)
实际上不可行,但是直到您构建了候选集之前,不消除不可行的候选者才会被消除,并且在此处构建候选集会导致硬错误。提议的CWG 2052决议将使这种情况成为SFINAE,也使原始代码有效。问题是-Clang也正在此处实现该标准的当前版本。