不相关的已删除运算符更改重载解析的行为

Unrelated deleted operator changes behaviour of overload resolution

本文关键字:重载 删除 运算符 不相关      更新时间:2023-10-16

我今天遇到了一个奇怪的情况,用某些参数声明一个已删除的运算符会改变看似无关的代码的行为。

我将其简化为以下内容。从这个开始:

namespace N 
{
enum E { A, B };
struct C 
{   
C(E);
private:
C(int);
};  
}
N::E operator|(N::E, N::E);
namespace N
{
void Waldo()
{   
C(A | B); 
}   
}

请注意,C有两个构造函数,一个公共构造函数和一个私有构造函数。此代码进行编译,表示正在选择公共重载,因此表达式A | B的类型为E。这反过来意味着operator|(N::E, N::E)已经匹配(否则AB将进行隐式转换为整数,A | B的类型将为int,并且私有构造函数将匹配

到目前为止还不错。现在我定义了一个新的枚举类型F,以及一个已删除的涉及F:的operator|

namespace N 
{
enum E { A, B };
struct C 
{   
C(E);
private:
C(int);
};  
}
N::E operator|(N::E, N::E);
namespace N
{
enum F {};
int operator|(F, int) = delete; 
void Waldo()
{   
C(A | B); 
}   
}

现在代码没有编译,说明C(int)是私有的。这表明现在A | B具有类型int,这意味着operator|(N::E, N::E)不再匹配。

为什么添加已删除的operator|(F, int)会阻止operator|(N::E, N::E)匹配

首先,请注意,被声明为deleted是不相关的,因为删除的函数仍然参与重载解析。

现在,转到过载解决方案。参见13.3.1.2/3:

三组候选函数,指定成员候选非成员构建

(没有候选成员,因为E不是类类型。)我们从中知道,运算符重载是由非限定查找发现的。因此,当我们查阅3.4.1[不合格查找]时,我们发现

一旦找到名称的声明,

名称查找就会结束。

由于在命名空间N中引入了第二个运算符重载,因此会首先找到它,并停止名称查找。此时,重载集由int N::operator|(N::F, int)和内置运算符组成。继续13.3.1.2/6:

过载解决的候选函数集是成员候选、非成员候选和内置候选的联合。

只有内建是可行的(因为您不能隐式地将E转换为F),因此会选择它。

问题的解决方案很简单。

operator|放在与类型相同的命名空间中。现在,ADL(自变量相关查找)开始了,它被发现,即使也有不相关的operator|可见。

活生生的例子。注意,尽管在namespace Z中使用了|,但还是发现了N::operator|

重载类型的自由运算符的合适位置是该类型所在的namespace,而不是全局命名空间。