C++ std::rel_ops的惯用用法
使用C++ std::rel_ops的惯用用法,c++,c++-standard-library,idioms,C++,C++ Standard Library,Idioms,使用std::rel_ops向类添加全套关系运算符的首选方法是什么 文档建议使用命名空间std::rel_ops,但这似乎存在严重缺陷,因为这意味着包括以这种方式实现的类的头也将使用定义的运算符向所有其他类添加完整的关系运算符,所以我认为首选的技术不是在 全部的boost::operator()中使用的技术似乎是常见的 解决方案 例如: #include "boost/operators.hpp" class SomeClass : private boost::equivalent<S
std::rel_ops
向类添加全套关系运算符的首选方法是什么
文档建议使用命名空间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;
}
#包括“boost/operators.hpp”
类SomeClass:private boost::等效,boost::完全有序
{
公众:
布尔算子b;
a=b;
a==b;
a!=b;
}
用户定义类的运算符重载的工作方式是通过依赖于参数的查找。ADL允许程序和库避免操作符重载造成全局名称空间混乱,但仍然允许方便地使用操作符;也就是说,没有显式的名称空间限定,这不可能使用中缀运算符语法a+b
,而是需要正常的函数语法您的_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 {}; } }
<当然>添加代码> MaxyRelopopsWorks >技术上,您自己会导致程序有未定义的行为,因为C++不允许用户程序向<代码> STD< /Cuff>添加声明。作为一个示例,说明这实际上有什么关系,以及为什么如果您这样做,您可能需要费劲验证您的实现是否确实能够与此添加一起正常工作,请考虑:
上面我展示了一个使相关操作有效的声明,该声明在\include
之后。有人可能天真地认为,在这里包含这一点并不重要,只要在使用操作符重载之前的某个时间包含了头,那么ADL就可以工作。规范当然没有这样的保证,但实际的实现却不是这样
LIBC++的CLAN,由于LIBC+++使用内联命名空间,将(IIUC)认为<代码> MaxOrrRyopopsx工作< /COD>在包含<代码> < /Cord>运算符重载的命名空间中处于不同的命名空间,除非<代码> <代码>声明<代码> STD::ReloOPs第一。这是因为,从技术上讲,std::_1::rel_ops
和std::rel_ops
是不同的命名空间,即使std::_1
是内联命名空间。但是如果clang看到rel_ops
的原始名称空间声明位于内联名称空间\uu 1
,那么它会将名称空间std{namespace rel_ops{
声明视为扩展std::\uu 1::rel_ops
而不是作为新名称空间
我相信这个名称空间扩展行为是CLAN扩展而不是C++所指定的,所以在其他实现中您甚至不能依赖它。特别是GCC不这样做,但幸运的是LIbSTDC++不使用内联命名空间。如果您不想依赖这个扩展,那么对于CLAN/LBC++,您可以编写:
#include <__config>
_LIBCPP_BEGIN_NAMESPACE_STD
namespace rel_ops { struct make_rel_ops_work {}; }
_LIBCPP_END_NAMESPACE_STD
#包括
_LIBCPP\u开始\u名称空间\u标准
命名空间rel_ops{struct make_rel_ops_work{};}
_LIBCPP_END_NAMESPACE_STD
但是很明显,您需要为您使用的其他库实现。我简单的make_rel_ops_work
声明适用于clang3.2/libc++、gcc4.7.3/libstdc++和VS2012。这不是最好的,但是您可以使用使用命名空间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
};
模板
结构MyType
{
T值;
friend bool operator添加rel_ops名称空间的问题在于,无论您是通过手动使用名称空间rel_ops;
进行添加,还是按照@bames53的回答自动进行添加,添加名称空间可能会对代码的某些部分产生意想不到的副作用。我最近发现这一点正如我使用@bames53解决方案已经有一段时间了,但是当我将一个基于容器的操作更改为使用反向迭代器而不是迭代器时(在多重映射中,但我怀疑它对于任何标准容器都是一样的),在使用!=比较两个迭代器时,突然出现编译错误。最终,我发现代码中包含rel_ops名称空间,这影响了反向迭代器的定义方式
使用boost可能是解决问题的一种方法,但正如@Tom所提到的,并不是每个人都愿意使用boost,包括我自己。因此我实现了自己的类来解决这个问题,我怀疑boost也是如何实现的,但我没有查看boost库
具体而言,我定义了以下结构:
template <class T>
struct add_rel_ops {
inline bool operator!=(const T& t) const noexcept {
const T* self = static_cast<const T*>(this);
return !(*self == t);
}
inline bool operator<=(const T& t) const noexcept {
const T* self = static_cast<const T*>(this);
return (*self < t || *self == t);
}
inline bool operator>(const T& t) const noexcept {
const T* self = static_cast<const T*>(this);
return (!(*self == t) && !(*self < t));
}
inline bool operator>=(const T& t) const noexcept {
const T* self = static_cast<const T*>(this);
return !(*self < t);
}
};
添加到add_rel_ops类以使其成为一个虚拟类。当然,这也将迫使MyClass
成为一个虚拟类,这就是为什么我不采用这种方法。使用命名空间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
};
template <class T>
struct add_rel_ops {
inline bool operator!=(const T& t) const noexcept {
const T* self = static_cast<const T*>(this);
return !(*self == t);
}
inline bool operator<=(const T& t) const noexcept {
const T* self = static_cast<const T*>(this);
return (*self < t || *self == t);
}
inline bool operator>(const T& t) const noexcept {
const T* self = static_cast<const T*>(this);
return (!(*self == t) && !(*self < t));
}
inline bool operator>=(const T& t) const noexcept {
const T* self = static_cast<const T*>(this);
return !(*self < t);
}
};
class MyClass : public add_rel_ops<MyClass> {
...stuff...
};
virtual ~add_rel_ops() noexcept = default;