在 boost::optional 中使用相等运算符
Using equality operators with boost::optional
我正在尝试为另一个命名空间中定义的类型 T 定义相等运算符,然后在 optional<T>
上使用相等运算符。在 clang (Apple LLVM 9.1.0) 上,此代码:
namespace nsp {
struct Foo {
};
}
bool operator==(const nsp::Foo& a, const nsp::Foo& b);
void foo() {
optional<nsp::Foo> a = none;
optional<nsp::Foo> b = none;
if (a == b)
;
}
结果出错:
/usr/local/include/boost/optional/detail/optional_relops.hpp:29:34: error: invalid operands to binary expression ('const nsp::Foo' and 'const nsp::Foo')
{ return bool(x) && bool(y) ? *x == *y : bool(x) == bool(y); }
~~ ^ ~~
MWE.cpp:40:19: note: in instantiation of function template specialization 'boost::operator==<what3words::engine::nsp::Foo>' requested here
if (a == b)
^
/usr/local/include/boost/optional/detail/optional_relops.hpp:28:6: note: candidate template ignored: could not match 'optional<type-parameter-0-0>' against 'const nsp::Foo'
bool operator == ( optional<T> const& x, optional<T> const& y )
发生了什么事?我的猜测是这与 Koenig 查找规则有关...
确实,您应该通过在 Foo
的关联命名空间内声明 operator==
实现来启用 ADL。这修复了:
#include <boost/optional.hpp>
namespace nsp {
struct Foo { };
bool operator==(const Foo&, const Foo&) { return false; }
}
int main() {
boost::optional<nsp::Foo> a;
boost::optional<nsp::Foo> b;
return (a == b)? 0 : 1;
}
立即修复
这样做:
namespace nsp {
bool operator==(const Foo& a, const Foo& b);
}
解决您的问题。
如果您可以控制 Foo
,您可以改为:
namespace nsp {
struct Foo {
friend bool operator==(const Foo& a, const Foo& b) {
return true;
}
};
}
如果 Foo
是模板 class,这是最优的。
你的解决方案出了什么问题
这里发生的事情是 optional
在 std
(或 boost
或其他)中,并且在该命名空间中它尝试执行 nsp::Foo == nsp::Foo
调用。
有一个 ==
不适用于 ::std
命名空间,因此它不会在 ::
中查找;一旦找到 any ==
它就会停止查找,即使参数完全不相关。它还在与参数关联的命名空间中查找 ==
——在本例中为 ::nsp
。但它也从不在 ::
中查找。
向类型添加运算符时,始终在类型的命名空间中定义运算符。
命名空间可以重新打开。因此,如果您无法控制头文件,则可以创建一个新的头文件,其中包含 ==
。此 ==
必须在调用 optional<nsp::Foo>::operator==
的每个点可见,或者您的程序由于 ODR 违规而格式错误(并且在这种情况下,还会生成编译器错误,这有助于避免 heizenbugs ).
长版
当您调用运算符(或函数)时,查找遵循几个简单的步骤。
首先它会在本地(在本地名称空间中)四处查看。如果它在那里找到任何东西,这个搜索就会停止。 (这包括注入命名空间的 using ns::identifier;
个名称,但通常不包括 using namespace foo;
)。即使函数或运算符不适用于所讨论的类型,也会发生这种“停止”; any ==
对于命名空间中任何类型的任何类型都会停止此搜索。
如果找不到匹配项,它将开始查找封闭的命名空间,直到找到 function/operator,或到达根命名空间。如果有 using namespace foo;
声明,这些命名空间中的 functions/operators 被认为是在 using namespace
位置和正在导入的命名空间的“共同父”命名空间中。 (所以 namespace foo
中的 using namespace std;
看起来像是 std
在 ::
中,而不是 foo
中)。
结果生成了一组候选重载决议。
接下来,ADL(参数相关查找)完成。检查所有 function/operator 个参数的关联命名空间。此外,还检查(递归)模板的所有类型参数的关联名称空间。
Operators/functions 匹配名称的被收集。对于 ADL,不检查父名称空间。
operators/functions 的这两个集合是重载解析的候选对象。
在您的例子中,调用 ==
的名称空间是 boost
。 boost
有很多 ==
运算符(即使它们不适用),因此 boost
中的所有 ==
都是候选对象。
接下来,我们检查参数的命名空间——在本例中为 nsp::Foo
。我们查看 nsp
并没有看到 ==
.
然后我们 运行 对这些重载解析。没有候选人工作,你得到一个编译器错误。
现在,当您将用户定义的 ==
移动到 namespace nsp
中时,它会被添加到 ADL 步骤中找到的 ==
集合中。当它匹配时,它被称为。
过载解决方案(它对候选人的作用)本身就是一个复杂的主题。简而言之,它试图找到涉及最少转换量的重载;如果两种情况完全匹配,则它更喜欢非模板而不是模板,并且更喜欢非可变而不是可变。
“最少的转换”和“完全正确”中有很多细节可能会误导程序员。最常见的是将 Foo
左值转换为 Foo const&
是少量转换,将其转换为 template<class T> T&&
或 T&
则没有转换。
我正在尝试为另一个命名空间中定义的类型 T 定义相等运算符,然后在 optional<T>
上使用相等运算符。在 clang (Apple LLVM 9.1.0) 上,此代码:
namespace nsp {
struct Foo {
};
}
bool operator==(const nsp::Foo& a, const nsp::Foo& b);
void foo() {
optional<nsp::Foo> a = none;
optional<nsp::Foo> b = none;
if (a == b)
;
}
结果出错:
/usr/local/include/boost/optional/detail/optional_relops.hpp:29:34: error: invalid operands to binary expression ('const nsp::Foo' and 'const nsp::Foo')
{ return bool(x) && bool(y) ? *x == *y : bool(x) == bool(y); }
~~ ^ ~~
MWE.cpp:40:19: note: in instantiation of function template specialization 'boost::operator==<what3words::engine::nsp::Foo>' requested here
if (a == b)
^
/usr/local/include/boost/optional/detail/optional_relops.hpp:28:6: note: candidate template ignored: could not match 'optional<type-parameter-0-0>' against 'const nsp::Foo'
bool operator == ( optional<T> const& x, optional<T> const& y )
发生了什么事?我的猜测是这与 Koenig 查找规则有关...
确实,您应该通过在 Foo
的关联命名空间内声明 operator==
实现来启用 ADL。这修复了:
#include <boost/optional.hpp>
namespace nsp {
struct Foo { };
bool operator==(const Foo&, const Foo&) { return false; }
}
int main() {
boost::optional<nsp::Foo> a;
boost::optional<nsp::Foo> b;
return (a == b)? 0 : 1;
}
立即修复
这样做:
namespace nsp {
bool operator==(const Foo& a, const Foo& b);
}
解决您的问题。
如果您可以控制 Foo
,您可以改为:
namespace nsp {
struct Foo {
friend bool operator==(const Foo& a, const Foo& b) {
return true;
}
};
}
如果 Foo
是模板 class,这是最优的。
你的解决方案出了什么问题
这里发生的事情是 optional
在 std
(或 boost
或其他)中,并且在该命名空间中它尝试执行 nsp::Foo == nsp::Foo
调用。
有一个 ==
不适用于 ::std
命名空间,因此它不会在 ::
中查找;一旦找到 any ==
它就会停止查找,即使参数完全不相关。它还在与参数关联的命名空间中查找 ==
——在本例中为 ::nsp
。但它也从不在 ::
中查找。
向类型添加运算符时,始终在类型的命名空间中定义运算符。
命名空间可以重新打开。因此,如果您无法控制头文件,则可以创建一个新的头文件,其中包含 ==
。此 ==
必须在调用 optional<nsp::Foo>::operator==
的每个点可见,或者您的程序由于 ODR 违规而格式错误(并且在这种情况下,还会生成编译器错误,这有助于避免 heizenbugs ).
长版
当您调用运算符(或函数)时,查找遵循几个简单的步骤。
首先它会在本地(在本地名称空间中)四处查看。如果它在那里找到任何东西,这个搜索就会停止。 (这包括注入命名空间的 using ns::identifier;
个名称,但通常不包括 using namespace foo;
)。即使函数或运算符不适用于所讨论的类型,也会发生这种“停止”; any ==
对于命名空间中任何类型的任何类型都会停止此搜索。
如果找不到匹配项,它将开始查找封闭的命名空间,直到找到 function/operator,或到达根命名空间。如果有 using namespace foo;
声明,这些命名空间中的 functions/operators 被认为是在 using namespace
位置和正在导入的命名空间的“共同父”命名空间中。 (所以 namespace foo
中的 using namespace std;
看起来像是 std
在 ::
中,而不是 foo
中)。
结果生成了一组候选重载决议。
接下来,ADL(参数相关查找)完成。检查所有 function/operator 个参数的关联命名空间。此外,还检查(递归)模板的所有类型参数的关联名称空间。
Operators/functions 匹配名称的被收集。对于 ADL,不检查父名称空间。
operators/functions 的这两个集合是重载解析的候选对象。
在您的例子中,调用 ==
的名称空间是 boost
。 boost
有很多 ==
运算符(即使它们不适用),因此 boost
中的所有 ==
都是候选对象。
接下来,我们检查参数的命名空间——在本例中为 nsp::Foo
。我们查看 nsp
并没有看到 ==
.
然后我们 运行 对这些重载解析。没有候选人工作,你得到一个编译器错误。
现在,当您将用户定义的 ==
移动到 namespace nsp
中时,它会被添加到 ADL 步骤中找到的 ==
集合中。当它匹配时,它被称为。
过载解决方案(它对候选人的作用)本身就是一个复杂的主题。简而言之,它试图找到涉及最少转换量的重载;如果两种情况完全匹配,则它更喜欢非模板而不是模板,并且更喜欢非可变而不是可变。
“最少的转换”和“完全正确”中有很多细节可能会误导程序员。最常见的是将 Foo
左值转换为 Foo const&
是少量转换,将其转换为 template<class T> T&&
或 T&
则没有转换。