今天我遇到了一个奇怪的情况,声明一个带有某些参数的已删除运算符会改变看似无关的代码的行为。
我将它减少到以下。从这开始:
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)
已匹配(否则A
和B
将隐式转换为整数,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)
被匹配?
答案 0 :(得分:5)
首先请注意,声明为delete
d是无关紧要的,因为删除的函数仍然参与重载解析。
现在,重载决议。参看13.3.1.2/3:
三组候选函数,指定成员候选者,非成员 候选人和内置候选人,构建
(没有成员候选者,因为E
不是类类型。)我们知道运算符重载是由非限定查找找到的。所以当我们查阅3.4.1(“不合格的查找”)时,我们发现
一旦找到名称的声明,名称查找就会结束。
由于在名称空间N
中引入了第二个运算符重载,因此首先找到它,并且名称查找停止。此时,重载集由您的int N::operator|(N::F, int)
和内置运算符组成。继续在13.3.1.2/6:
重载决议的候选函数集是成员候选人,非成员候选人和内置候选人的联合。
只有内置版是可行的(因为你无法隐式地将E
转换为F
),因此它被选中。
答案 1 :(得分:1)
解决问题的方法很简单。
将operator|
放在与类型相同的命名空间中。现在,ADL(依赖于参数的查找)启动,即使 ,也可以看到,也可以看到不相关的operator|
。
Live example。请注意,尽管N::operator|
中使用了|
,但仍会找到namespace Z
。
为类型重载自由运算符的正确位置是类型所在的namespace
,而不是全局命名空间。