以下简化示例以gccVisual 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 2052CWG issue 1391

首先,是CWG1391。在遇到x + a时,通常的名称查找会发现其他重载,

template <typename T> double operator+ (T, double);

模板参数推导是通过将T+的lhs类型匹配的,即double进行的,因此推导Tdouble。第二个参数的类型不包含模板参数,因此在当前规则下不考虑。可以肯定的是,N::A不能转换为double,因此产生的特化是不可行的,但是当前的规则说模板参数推导对此并不在意。将以过载解析的方式进行处理。

CWG 1391的拟议决议,除其他外,在标准中增加了新段落:



换句话说,如果不能将与非依赖性参数(a)对应的参数(在我们的情况下为double)转换为参数的类型,则推论将简单地失败。因此,在我们的案例中,CWG1391之后的模板参数推导将因这种重载而失败,并且一切都会好起来的。

Clang执行当前规则,但是,推导成功完成T = double,替换发生,并且我们遇到了CWG2052。引用Richard Smith(Clang开发人员)的文章:



这不是SFINAE上下文,所以程序格式不正确,而是
而不是选择内置运算符。

在这种情况下,没有转换,因此推断出的operator+(double, double)实际上不可行,但是直到您构建了候选集之前,不消除不可行的候选者才会被消除,并且在此处构建候选集会导致硬错误。

提议的CWG 2052决议将使这种情况成为SFINAE,也使原始代码有效。问题是-Clang也正在此处实现该标准的当前版本。

09-19 05:33