习惯使用std :: rel_ops

Man*_*rse 47 c++ idioms c++-standard-library

使用std::rel_ops将完整的关系运算符集添加到类的首选方法是什么?

文档建议一个using namespace std::rel_ops,但是这似乎是有严重缺陷的,因为这将意味着包括以这种方式实现的类的头也将增加全关系运算符所有其他类与定义operator<operator==,即使是不希望的.这有可能以令人惊讶的方式改变代码的含义.

作为旁注 - 我一直在使用Boost.Operators来做这件事,但我仍然对标准库感到好奇.

bam*_*s53 32

运算符重载用户定义类的方式是通过参数依赖查找来工作的.ADL允许程序和库避免使用运算符重载来混淆全局命名空间,但仍允许方便地使用运算符; 也就是说,没有显式的名称空间限定,这是不可能使用中缀运算符语法a + b,而是需要正常的函数语法your_namespace::operator+ (a, b).

但是,ADL并不只是在任何地方搜索任何可能的操作员过载.ADL仅限于查看"关联"类和名称空间.问题std::rel_ops在于,如指定的那样,此命名空间永远不能是标准库外部定义的任何类的关联命名空间,因此ADL无法使用此类用户定义的类型.

但是,如果你愿意作弊,你就可以开始std::rel_ops工作.

关联的命名空间在C++ 11 3.4.2 [basic.lookup.argdep]/2中定义.出于我们的目的,重要的事实是基类是其成员的名称空间是继承类的关联名称空间,因此ADL将检查这些名称空间以获取适当的函数.

所以,如果如下:

#include <utility> // rel_ops
namespace std { namespace rel_ops { struct make_rel_ops_work {}; } }
Run Code Online (Sandbox Code Playgroud)

是(以某种方式)找到进入翻译单元的方式,然后在支持的实现(参见下一节),然后您可以定义自己的类类型,如下所示:

namespace N {
  // inherit from make_rel_ops_work so that std::rel_ops is an associated namespace for ADL
  struct S : private std::rel_ops::make_rel_ops_work {};

  bool operator== (S const &lhs, S const &rhs) { return true; }
  bool operator< (S const &lhs, S const &rhs) { return false; }
}
Run Code Online (Sandbox Code Playgroud)

然后ADL将适用于您的类类型,并将找到运算符std::rel_ops.

#include "S.h"

#include <functional> // greater

int main()
{
  N::S a, b;   

  a >= b;                      // okay
  std::greater<N::s>()(a, b);  // okay
}
Run Code Online (Sandbox Code Playgroud)

当然,在make_rel_ops_work技术上添加自己会导致程序具有未定义的行为,因为C++不允许用户程序添加声明std.作为一个实际上如何重要的例子以及为什么,如果你这样做,你可能想要验证你的实现确实在这个添加中正常工作的麻烦,考虑:

我在上面显示的声明make_rel_ops_work下面#include <utility>.人们可能天真地期望在这里包含它并不重要,并且只要在使用运算符重载之前的某个时间包含头,那么ADL将起作用.规范当然没有这样的保证,并且实际的实施情况并非如此.

由于libc ++使用内联命名空间,因此libc ++将会(IIUC)认为声明属于make_rel_ops_work包含<utility>运算符重载的命名空间的不同命名空间,除非首先<utility>声明std::rel_ops.这是因为从技术上讲,即使是内联命名空间std::__1::rel_ops,std::rel_ops它们也是不同的命名std::__1空间.但是如果clang看到原始名称空间声明rel_ops是在内联名称空间中__1,那么它会将namespace std { namespace rel_ops {声明视为扩展std::__1::rel_ops而不是新名称空间.

我相信这个命名空间扩展行为是一个clang扩展而不是C++指定的,所以你可能甚至无法在其他实现中依赖它.特别是gcc不会这样,但幸运的是libstdc ++不使用内联命名空间.如果您不想依赖此扩展,那么对于clang/libc ++,您可以编写:

#include <__config>
_LIBCPP_BEGIN_NAMESPACE_STD
namespace rel_ops { struct make_rel_ops_work {}; }
_LIBCPP_END_NAMESPACE_STD
Run Code Online (Sandbox Code Playgroud)

但显然你需要实现你使用的其他库.我更简单地声明make_rel_ops_work了clang3.2/libc ++,gcc4.7.3/libstdc ++和VS2012的工作原理.

  • 或者你可以编写自己版本的 `std::rel_ops` (可能通过依赖 `std::rel_ops` 来实现)并避免 UB (2认同)

Jam*_*nze 24

我认为首选技术根本不使用std::rel_ops.boost::operator(链接)中使用的技术似乎是通常的解决方案.

例:

#include "boost/operators.hpp"

class SomeClass : private boost::equivalent<SomeClass>, boost::totally_ordered<SomeClass>
{
public:
    bool operator<(const SomeClass &rhs) const
    {
        return someNumber < rhs.someNumber;
    }
private:
    int someNumber;
};

int main()
{
    SomeClass a, b;
    a < b;
    a > b;
    a <= b;
    a >= b;
    a == b;
    a != b;
}
Run Code Online (Sandbox Code Playgroud)

  • 这实际上并没有回答这个问题.许多C++开发项目由于其大小和复杂性而不包括boost. (15认同)
  • 答案可以扩展到,你知道,_describe_在`boost :: operator`中使用的技术.一般而言,虽然答案是正确的,因为使用`std :: rel_ops`的惯用(或其他任何)方式只是为了明白而不是.甚至用运算符滚动自己的类模板也更好. (9认同)