使用具有boost :: optional的相等运算符

Moh*_*han 5 c++ boost argument-dependent-lookup

我正在尝试为另一个命名空间中定义的类型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)
            ;
    }
Run Code Online (Sandbox Code Playgroud)

导致错误:

/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 )
Run Code Online (Sandbox Code Playgroud)

发生了什么?我的猜测是它与Koenig查找规则有关...

Yak*_*ont 5

立即解决

做这个:

namespace nsp {
  bool operator==(const Foo& a, const Foo& b);
}
Run Code Online (Sandbox Code Playgroud)

解决你的问题.

如果你有控制权Foo,你可以改为:

namespace nsp {
  struct Foo {
    friend bool operator==(const Foo& a, const Foo& b) {
      return true;
    }
  };
}
Run Code Online (Sandbox Code Playgroud)

如果Foo是模板类,这是最佳的.

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

这里发生的optional是在std(或者boost其他)中,并且在该命名空间中它尝试进行nsp::Foo == nsp::Foo 调用.

有一个==不适用于::std命名空间,所以它不会查找::; 一旦它发现任何 ==它停止寻找,即使参数完全不相关.它还==在与参数关联的名称空间中查找- 在本例中::nsp.但它也从未在::这里看过.

将运算符添加到类型时,始终在类型的名称空间中定义运算符.

可以重新打开命名空间.因此,如果您无法控制头文件,则可以使用其中创建新的头文件==.这==必须在optional<nsp::Foo>::operator==调用的每个点都可见,或者由于ODR违规而导致程序生成错误(在这种情况下,还会生成编译器错误,这对于避免heizenbugs很有用).

长版

当您调用操作符(或函数)时,查找遵循几个简单的步骤.

首先,它在本地(在本地命名空间中)环顾四周.如果它在那里找到任何东西,则此搜索停止.(这包括using ns::identifier;注入命名空间的名称,但通常不包括using namespace foo;).即使函数或运算符不适用于所讨论的类型,也会发生"停止"; 任何==类型的名称空间中的任何类型都会停止此搜索.

如果找不到匹配项,它将开始查找封闭的命名空间,直到找到函数/运算符或到达根命名空间.如果存在using namespace foo;声明,则这些命名空间中的函数/运算符将被视为位于using namespace要导入的位置和命名空间的"公共父"命名空间中.(因此using namespace std;,namespace foo它似乎std是在::,而不是foo).

结果生成一组候选重载决策.

接下来,完成ADL(参数依赖查找).检查所有函数/运算符参数的关联名称空间.此外,还会检查(递归)模板的所有类型参数的关联名称空间.

收集与名称匹配的运算符/函数.对于ADL,不检查父命名空间.

这两个运算符/函数集合是您重载决策的候选者.

在您的情况下,==调用的名称空间是boost. boost有大量的==运营商(即使他们不适用),因此所有的==boost考生.

接下来,我们检查参数的命名空间 - nsp::Foo在本例中.我们看着nsp,看不到==.

然后我们对这些进行重载解析.没有候选人工作,并且您得到编译器错误.

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

重载决议(它与候选人的关系)是它自己的复杂主题.简短形式是它试图找到涉及最少量转换的重载; 如果两个案例完全匹配,则它更喜欢模板上的非模板和非变量的非变量.

"最少量的转换"和"确切"的内容有很多可以误导程序员的细节.最常见的是将Foo左值转换为Foo const&少量转换,将其转换为template<class T> T&&T&不转换.

  • @Mohan这个问题的答案是"你做不到".对于通用类型`T`,没有合法的方法可以做到这一点; 我可以解释原因,但这样做会有新的问题/答案,或者你可以相信我.您可以为自己的`Foo`(在这种情况下,把它放在`Foo`的命名空间)类型做到这一点,而不是一个泛型类型`T`. (2认同)