习惯于使用std :: rel_ops

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

这个文档build议using namespace std::rel_ops ,但是这似乎有很深的缺陷,因为这意味着包含以这种方式实现的类的头文件也会将完整的关系运算符添加到具有定义的运算符<运算符==,即使这不是所期望的。 这有可能以惊人的方式改变代码的含义。

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

我认为首选技术是不使用std::rel_opsboost::operator ( link )中使用的技术似乎是通常的解决scheme。

例:

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

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

然而,ADL并不是在任何地方search任何可能的运营商超载。 ADL仅限于查看“关联”类和名称空间。 std::rel_ops的问题在于,如指定的那样,这个命名空间永远不可能是在标准库之外定义的任何类的关联命名空间,因此ADL不能使用这种用户定义的types。

但是,如果你愿意作弊,你可以使std::rel_ops工作。

关联的名称空间在C ++ 11 3.4.2 [basic.lookup.argdep] / 2中定义。 就我们的目的而言,重要的事实是基类所属的名称空间是inheritance类的关联名称空间,因此ADL将检查这些名称空间以获取适当的function。

所以,如果以下情况:

 #include <utility> // rel_ops namespace std { namespace rel_ops { struct make_rel_ops_work {}; } } 

(以某种方式)find它的方式到一个翻译单元,然后在支持的实现(见下一节),然后你可以定义你自己的类types,如:

 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将为您的类types工作,并会在std::rel_opsfind运算符。

 #include "Sh" #include <functional> // greater int main() { N::S a, b; a >= b; // okay std::greater<N::s>()(a, b); // okay } 

当然,在make_rel_ops_work自己添加make_rel_ops_work会导致程序有未定义的行为,因为C ++不允许用户程序向std添加声明。 作为一个例子,这实际上是重要的,为什么,如果你这样做,你可能想要validation你的实现确实可以正确地与这个添加一起工作的麻烦,考虑:

上面我显示了#include <utility> make_rel_ops_work声明。 有人可能会天真地期望在这里包括这个并不重要,只要在使用运算符重载之前的某个时间包含头文件,ADL就可以工作。 规范当然没有这样的保证,有实际的情况下不是这样的实现。

因为libc ++使用了内联名字空间,所以(IIUC)会认为make_rel_ops_work声明在与包含<utility>运算符重载的命名空间不同的命名空间中,除非<utility>声明了std::rel_ops第一。 这是因为,从技术上讲,即使std::__1是内联命名空间, std::__1::rel_opsstd::rel_ops也是不同的命名空间。 但是,如果clang认为rel_ops的原始名称空间声明位于内联名称空间__1 ,那么它将把namespace std { namespace rel_ops { declaration作为扩展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 

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

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

 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<=如果它是为types定义的,则返回到std::rel_ops定义的std::rel_ops

但是,这仍然是一个痛苦,因为您仍然需要为每个比较运算符编写一个函数。