C++运算符重载和关联的命名空间

dat*_*ats 5 c++ namespaces operator-overloading clang argument-dependent-lookup

以下简化示例编译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;
}
Run Code Online (Sandbox Code Playgroud)

在我看来,ADL可以找到模板operator+名称空间N.

为什么不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
Run Code Online (Sandbox Code Playgroud)

当然,这个例子是从现实代码中简化而来的.目的是在命名空间N中定义的任何类都有一个带有double的重载operator +.

T.C*_*.C. 5

这是由两个不同的CWG问题引起的:CWG问题2052CWG问题1391.

首先,CWG 1391.在遇到时x + a,通常的名称查找发现,除了其他重载,

template <typename T> double operator+ (T, double);
Run Code Online (Sandbox Code Playgroud)

模板参数推导由匹配执行T到的LHS的类型+,这就是double,所以这个推导出Tdouble.第二个参数的类型不包含模板参数,因此在当前规则下不予考虑.可以肯定的是,N::A不能转换成a double,所以由此产生的专业化是不可行的,但是现行的规则说模板参数推论并不关心这一点; 这将在重载决议中处理.

除其他事项外,对CWG 1391的拟议决议为该标准增加了一个新段落:

如果对包含参与模板参数推导的模板参数的所有参数进行推导成功,并且显式指定,推导或从默认模板参数获取所有模板参数,则将剩余参数与相应参数进行比较.对于P在替换任何显式指定的模板参数之前具有非依赖类型的每个剩余参数,如果相应的参数A不能隐式转换为P,则推导失败.[ 注意:在重载解析期间,将检查具有依赖类型的参数,其中模板参数不参与模板参数推导,以及由于替换显式指定的模板参数而变为非依赖的参数.- 结束说明 ]

换句话说,如果a对应于非依赖参数(double)的参数(在我们的例子中)不能转换为参数的类型,则演绎将失败.因此,在我们的情况下,CWG1391后模板参数推导将因此过载而失败,并且一切都会很好.

但是,Clang实现了当前的规则,因此扣除成功T = double,替换发生,我们遇到CWG 2052.引用Richard Smith(Clang开发)的写入:

在一个例子中

  struct A { operator int(); };
  template<typename T> T operator<<(T, int);
  void f(A a) { 1 << a; }
Run Code Online (Sandbox Code Playgroud)

模板参数推导对运算符模板成功,生成签名operator<<(int,int).根据14.8.3 [temp.over]段落1合成结果并将其添加到重载集中.但是,这违反了第13段[over.oper]第6段的要求,

运算符函数应该是非静态成员函数,或者是非成员函数,其至少具有一个类型为类的参数,对类的引用,枚举或对枚举的引用.

这不是SFINAE环境,因此程序格式不正确,而不是选择内置运算符.

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

CWG 2052的拟议决议将使SFINAE成为案例 - 也使原始代码有效.问题是 - Clang也在这里实施当前版本的标准.