2011-06-03 62 views
37

使用std :: rel_ops将全套关系运算符添加到类中的首选方法是什么?std :: rel_ops的习惯性使用

This文档建议一个using namespace std::rel_ops,但是这似乎是有严重缺陷的,因为这将意味着包括以这种方式实现的类的头也将增加全关系运算符所有其他类与定义的操作<和运营商==,即使那不是我们想要的。这有可能以惊人的方式改变代码的含义。

作为一个方面说明 - 我一直在使用Boost.Operators来做到这一点,但我仍然对标准库感到好奇。

+10

'使用命名空间std :: rel_ops'的另一个问题是操作符不考虑参数相关的查找。这意味着,例如'std :: greater '将无法编译(而如果在与'my_type'相同的名称空间或全局名称空间中定义了合适的'operator>',它将会成功)。 – 2011-06-03 14:17:40

+0

@MikeSeymour我已经添加了一个(不可移植的spec-wise,但在实践中相当便携)解决方案,使ADL与rel_ops一起工作。 – bames53 2014-02-13 20:18:56

回答

20

我认为首选技术不是使用std::rel_ops在 所有。 boost::operatorlink)中使用的技术似乎是通常的 解决方案。

示例:

为用户定义的类
#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; 
} 
+11

这实际上并没有回答这个问题。由于其大小和复杂性,许多C++开发项目不包括增强功能。 – 2016-02-16 00:29:10

+7

答案可以扩展到,你知道,_describe_在'boost :: operator'中使用的技术。总的来说,尽管答案是正确的,但是使用'std :: rel_ops'的惯用方法(或者其他任何方法)完全不适用。即使使用操作员来滚动您自己的类模板也会更好。 – 2016-03-13 20:59:38

27

的方式操作符重载是为了工作是通过参数依赖查找。 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 {}; } } 

是:(莫名其妙)找到进入翻译单元,然后在支持实现(见下节),那么你可以定义自己的类的类型,如下所示:

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; } 
} 

然后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 
} 

过程中添加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_opsstd::rel_ops是不同的名称空间,即使std::__1是内联名称空间。但是,如果clang看到rel_ops的原始名称空间声明位于内联名称空间__1中,那么它会将namespace std { namespace rel_ops {声明视为扩展std::__1::rel_ops而不是作为新的名称空间。

我相信这个命名空间扩展行为是一个铿锵声扩展,而不是由C++指定的,因此您甚至可能无法在其他实现中依赖此。特别是gcc没有这样的行为,但幸运的是libstdC++不使用内联命名空间。如果你不想靠这个扩展则铛/的libC++,你可以这样写:

#include <__config> 
_LIBCPP_BEGIN_NAMESPACE_STD 
namespace rel_ops { struct make_rel_ops_work {}; } 
_LIBCPP_END_NAMESPACE_STD 

但很明显,那么你就需要为你使用其他库的实现。我的简单声明make_rel_ops_work适用于clang3.2/libC++,gcc4.7.3/libstdC++和VS2012。

+0

或者你也可以编写你自己的'std :: rel_ops'版本(可能通过回退'std :: rel_ops'来实现)并避免UB – Justin 2017-12-22 23:27:10

1

这不是最好的,但是您可以使用using namespace std::rel_ops作为实现您的类型的比较运算符的实现细节。例如:

template <typename T> 
struct MyType 
{ 
    T value; 

    friend bool operator<(MyType const& lhs, MyType const& rhs) 
    { 
     // The type must define `operator<`; std::rel_ops doesn't do that 
     return lhs.value < rhs.value; 
    } 

    friend bool operator<=(MyType const& lhs, MyType const& rhs) 
    { 
     using namespace std::rel_ops; 
     return lhs.value <= rhs.value; 
    } 

    // ... all the other comparison operators 
}; 

通过使用using namespace std::rel_ops;,我们允许ADL查找operator<=,如果它是该类型定义,但是回落到在std::rel_ops定义,否则所述一个。

尽管如此,仍然需要为每个比较运算符编写一个函数。