Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
menu search
person
Welcome To Ask or Share your Answers For Others

Categories

What is the preferred method of using std::rel_ops to add the full set of relational operators to a class?

This documentation suggests a using namespace std::rel_ops, but this seems to be deeply flawed, as it would mean that including the header for the class implemented in this way would also add full relational operators to all other classes with a defined operator< and operator==, even if that was not desired. This has the potential to change the meaning of code in surprising ways.

As a side note - I have been using Boost.Operators to do this, but I am still curious about the standard library.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
128 views
Welcome To Ask or Share your Answers For Others

1 Answer

The way operator overloads for user defined classes was meant to work is via argument dependent lookup. ADL allows programs and libraries to avoid cluttering up the global namespace with operator overloads, but still allow convenient use of the operators; That is, without explicit namespace qualification, which is not possible to do with the infix operator syntax a + b and would instead require normal function syntax your_namespace::operator+ (a, b).

ADL, however, doesn't just search everywhere for any possible operator overload. ADL is restricted to look only at 'associated' classes and namespaces. The problem with std::rel_ops is that, as specified, this namespace can never be an associated namespace of any class defined outside the standard library, and therefore ADL cannot work with such user defined types.

However, if you're willing to cheat you can make std::rel_ops work.

Associated namespaces are defined in C++11 3.4.2 [basic.lookup.argdep] /2. For our purposes the important fact is that the namespace of which a base class is a member is an associated namespace of the inheriting class, and thus ADL will check those namespaces for appropriate functions.

So, if the following:

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

were to (somehow) find its way into a translation unit, then on supported implementations (see next section) you could then define your own class types like so:

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

And then ADL would work for your class type and would find the operators in 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
}

Of course adding make_rel_ops_work yourself technically causes the program to have undefined behavior because C++ does not allow user programs to add declarations to std. As an example of how that actually does matter and why, if you do this, you may want to go to the trouble of verifying that your implementation does in fact work properly with this addition, consider:

Above I show a declaration of make_rel_ops_work that follows #include <utility>. One might naively expect that including this here doesn't matter and that as long as the header is included sometime prior to the use of the operator overloads, then ADL will work. The spec of course makes no such guarantee and there are actual implementations where that is not the case.

clang with libc++, due to libc++'s use of inline namespaces, will (IIUC) consider that declaration of make_rel_ops_work to be in a distinct namespace from the namespace containing the <utility> operator overloads unless <utility>'s declaration of std::rel_ops comes first. This is because, technically, std::__1::rel_ops and std::rel_ops are distinct namespaces even if std::__1 is an inline namespace. But if clang sees that the original namespace declaration for rel_ops is in an inline namespace __1, then it will treat a namespace std { namespace rel_ops { declaration as extending std::__1::rel_ops rather than as a new namespace.

I believe this namespace extension behavior is a clang extension rather than specified by C++, so you may not even be able to rely on this in other implementations. In particular gcc does not behave this way, but fortunately libstdc++ doesn't use inline namespaces. If you don't want to rely on this extension then for clang/libc++ you can write:

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

but obviously then you'll need implementations for other libraries you use. My simpler declaration of make_rel_ops_work works for clang3.2/libc++, gcc4.7.3/libstdc++, and VS2012.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
...