【问题标题】:Using equality operators with boost::optional使用带有 boost::optional 的相等运算符
【发布时间】:2018-08-21 14:41:42
【问题描述】:

我正在尝试为在另一个命名空间中定义的类型 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 查找规则有关......

【问题讨论】:

  • 长话短说:在与结构相同的命名空间中定义相等运算符。

标签: c++ boost argument-dependent-lookup


【解决方案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 是一个模板类,这是最佳的。

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

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

有一个== 不适用于::std 命名空间,因此它不会在:: 中查找;一旦找到 any == 它就会停止查找,即使参数完全不相关。它还在与参数关联的命名空间中查找==——在本例中为::nsp。但它也从不在这里查看::

向类型添加运算符时,始终在类型的命名空间中定义运算符。

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

长版

当您调用运算符(或函数)时,查找遵循几个简单的步骤。

首先它在本地(在本地命名空间中)四处查看。如果它在那里找到任何东西,这个搜索就会停止。 (这包括注入命名空间的using ns::identifier; 名称,但通常不包括using namespace foo;)。即使函数或运算符不适用于相关类型,也会发生这种“停止”;命名空间中任何类型的任何 == 都会停止此搜索。

如果找不到匹配项,它会开始查找封闭的命名空间,直到找到函数/运算符,或到达根命名空间。如果有 using namespace foo; 声明,则这些命名空间中的函数/运算符被认为位于 using namespace 位置和正在导入的命名空间的“公共父”命名空间中。 (所以namespace foo 中的using namespace std; 看起来就像std:: 中,而不是在foo 中)。

结果生成一个重载解决方案的候选集合。

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

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

这两个运算符/函数集合是您的重载解决方案的候选者。

在您的情况下,调用== 的命名空间是boostboost 有很多 == 运算符(即使它们不适用),所以 boost 中的所有 == 都是候选者。

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

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

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

重载解决方案(它对候选人的作用)是它自己的复杂主题。简短的形式是它试图找到涉及最少转换的重载;如果两个案例完全匹配,那么它更喜欢非模板而不是模板和非可变而不是可变。

“最少的转换”和“准确”中有很多细节会误导程序员。最常见的是将Foo左值转换为Foo const&amp;是少量转换,将其转换为template&lt;class T&gt; T&amp;&amp;T&amp;是没有转换的。

【讨论】:

  • 确实是重复的,但我找不到任何规范。是时候创建一个了吗?
  • @SergeyA stackoverflow.com/questions/3891402/… 很接近,但它问的是“放在哪里”而不是“出了什么问题”。我不确定对所有 Koenig 查找规则工作和最佳实践等的完整描述是否是一个很好的重复目标;这将是一本书的章节。
  • 我认为,我们可以使这个成为规范,但我也认为,我们需要稍微详细说明为什么在这种情况下不会查找全局命名空间。还可以添加一些概括(提到stdboost 命名空间中的所有常见运算符都有大量重载)
  • @SergeyA 好吧,我试着用非标准语言写一个长版本。肯定有错误、遗漏和简化。
  • @Mohan 这个问题的答案是“你不能”。对于泛型类型T,没有好的合法方法可以做到这一点;我可以解释原因,但这样做需要一个新的问题/答案,或者你可以相信我。您可以为您拥有的类型 Foo 执行此操作(在这种情况下,将其放在 Foo 的命名空间中),但不适用于泛型类型 T
【解决方案2】:

确实,您应该通过在 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;
}

【讨论】:

  • 我认为在类定义中将二元运算符声明为友元是一种很好的做法,尤其是在使用模板时。
  • @Gold 我也是。但是,在使用模板时,将它们声明为朋友可能会遇到一些编译器限制
猜你喜欢
  • 2021-07-03
  • 1970-01-01
  • 2021-09-08
  • 2015-09-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多