在 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,这是最优的。

你的解决方案出了什么问题

这里发生的事情是 optionalstd(或 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 的这两个集合是重载解析的候选对象。

在您的例子中,调用 == 的名称空间是 boostboost 有很多 == 运算符(即使它们不适用),因此 boost 中的所有 == 都是候选对象。

接下来,我们检查参数的命名空间——在本例中为 nsp::Foo。我们查看 nsp 并没有看到 ==.

然后我们 运行 对这些重载解析。没有候选人工作,你得到一个编译器错误。

现在,当您将用户定义的 == 移动到 namespace nsp 中时,它会被添加到 ADL 步骤中找到的 == 集合中。当它匹配时,它被称为。

过载解决方案(它对候选人的作用)本身就是一个复杂的主题。简而言之,它试图找到涉及最少转换量的重载;如果两种情况完全匹配,则它更喜欢非模板而不是模板,并且更喜欢非可变而不是可变。

“最少的转换”和“完全正确”中有很多细节可能会误导程序员。最常见的是将 Foo 左值转换为 Foo const& 是少量转换,将其转换为 template<class T> T&&T& 则没有转换。