Interested? Simplify usage of std::rel_ops

Hi, I have a very simple header to simplify use of std::rel_ops with user defined classes, and I thought it might be interesting for inclusion in boost. It seems to work perfectly on gcc 4.0.2 20050901 (prerelease) (SUSE Linux), and I believe it's standard conforming. I haven't checked it with any other compiler, though. The usage is simple: You just write (with my current header, which of course would have to be adapted for boost): ---------------------------------------------------------------------- #include "relops.h" class MyClass: rel::ops // doesn't matter if you use public or private inheritance { // ... }; bool operator<(MyClass const&, MyClass const&) { /* ... */ } bool operator==(MyClass const&, MyClass const&) { /* ... */ } // now you can use all comparison operators: int main() { MyClass a, b; bool b = (a > b) // uses std::rel_ops::operator> } ---------------------------------------------------------------------- operator< and operator== of course can be defined anywhere the std::rel_ops operators will find them (e.g. they could be defined as class members instead). The header is obviously too simple to warrant its own boost library, but I could imagine it to be included e.g. in Boost.Utility. Alternatively it could be extended to support other typical operator implementations not covered by the standard, e.g. defining operator@ in terms of operator@=. Here's the actual code of the header: ---------------------------------------------------------------------- #ifndef RELOPS_H_INC #define RELOPS_H_INC #include <utility> namespace rel { using std::rel_ops::operator!=; using std::rel_ops::operator>; using std::rel_ops::operator>=; using std::rel_ops::operator<=; class ops {}; } #endif ---------------------------------------------------------------------- The trick is that by deriving from class rel::ops, you get the namespace rel looked into during Koenig lookup for function calls, esp. operators, involving your class type. Now, in namespace rel, the operators from std::rel_ops are found due to the explicit using declarations (obviously it would have been easier just to add class ops into std::rel_ops, but that's unfortunately prohibited by the standard). Deriving from class ops doesn't bring anything into your class than the additional Koenig lookup target, and with empty base class optimization it shouldn't affect your class otherwise as well. So what do you think?

On 7/13/06, Christopher Eltschka <celtschk@web.de> wrote:
operator< and operator== of course can be defined anywhere the std::rel_ops operators will find them (e.g. they could be defined as class members instead).
Note that you really only have to define operator< as operator== could be written as template<class T, class U> bool operator==(const T &lhs, const U &rhs) { return !(lhs < rhs) && !(rhs < lhs); } and the user could still define operator== themselves for optimization purposes (then you should make sure to define operator!= in terms of operator==). Are you going to allow comparision of different types (like I showed above) or strictly comparision among objects of the same type?
Alternatively it could be extended to support other typical operator implementations not covered by the standard, e.g. defining operator@ in terms of operator@=.
Sounds good. Kevin Spinar

Kevin Spinar schrieb:
On 7/13/06, Christopher Eltschka <celtschk@web.de> wrote:
operator< and operator== of course can be defined anywhere the std::rel_ops operators will find them (e.g. they could be defined as class members instead).
Note that you really only have to define operator< as operator== could be written as
template<class T, class U> bool operator==(const T &lhs, const U &rhs) { return !(lhs < rhs) && !(rhs < lhs); }
and the user could still define operator== themselves for optimization purposes (then you should make sure to define operator!= in terms of operator==).
I would have expected an ambiguity in that case (because both are found with Koenig lookup), but a quick test with operator!= showed that at least with gcc, indeed it works! Thinking about it, it probably works because the one in std::rel_ops is a template, so the non-template operator== takes precedence. I'm not sure if there are reasonable cases where this would break with a templated user-defined operator. However, one could make different base classes providing dofferent set of operators, say class A: rel::ops_from_less { ... } derived all operators from operator< and class A: rel::ops_from_less_and_equal { ... } doesn't derive equal from less. One could also make a version which derives all those operators from a "3-way comparison function" a la strcmp. In that case one would have to select a standardized name for that function as well. Maybe just "compare3way"? In that case, maybe it would also make sense to define that function for the other cases (i.e. automatically derive compare3way from operator<).
Are you going to allow comparision of different types (like I showed above) or strictly comparision among objects of the same type?
Well, at the moment I just delegate to std::rel_ops, so only comparing strictly the same type is supported. However, it's trivial to change this (i.e. write those functions again with two separate template type arguments) so that arbitrary types can be compared. However I'm not sure if it's a good idea: After all, it might trigger in a situation where we don't want it (I'm not sure if such a situation could happen, however it's something one should consider).
Alternatively it could be extended to support other typical operator implementations not covered by the standard, e.g. defining operator@ in terms of operator@=.
Sounds good.
Doing some brainstorming, I get the following candidate list for useful automatic operators: * comparison operators from operator< and operator== * operator @ from operator @= * posfix operator++/-- from prefix one * unary operator+ (returning just the object) * maybe also binary - from + and unary -? Any other good candidates? Christopher Eltschka

How is this different from boost::totally_ordered which I used to implement BOOST_STRONG_TYPEDEF? Robert Ramey

Christopher Eltschka ha scritto:
Doing some brainstorming, I get the following candidate list for useful automatic operators:
* comparison operators from operator< and operator== * operator @ from operator @= * posfix operator++/-- from prefix one * unary operator+ (returning just the object) * maybe also binary - from + and unary -?
Any other good candidates?
That's good, so good that we already have a header in Boost that does precisely all that and more. It's <boost/operators.hpp>, documented at <http://www.boost.org/libs/utility/operators.htm>. Ganesh

Alberto Ganesh Barbati schrieb:
Christopher Eltschka ha scritto:
Doing some brainstorming, I get the following candidate list for useful automatic operators:
* comparison operators from operator< and operator== * operator @ from operator @= * posfix operator++/-- from prefix one * unary operator+ (returning just the object) * maybe also binary - from + and unary -?
Any other good candidates?
That's good, so good that we already have a header in Boost that does precisely all that and more. It's <boost/operators.hpp>, documented at <http://www.boost.org/libs/utility/operators.htm>.
Ah, sorry, although I looked into boost before writing the mail, I somehow missed that, despite its obvious name. However I see that the boost version forces you to explicitly repeat the class name, i.e. you have to write class X: boost::operators<X> { ... }; instead of just class X: boost::operators { ... }; Is there any specific reason to do so? After all, it seems to work well without that extra burden. Any specific example where I could get problems with my version? Thanks, Christopher Eltschka

Christopher Eltschka ha scritto:
Ah, sorry, although I looked into boost before writing the mail, I somehow missed that, despite its obvious name. However I see that the boost version forces you to explicitly repeat the class name, i.e. you have to write
class X: boost::operators<X> { ... };
instead of just
class X: boost::operators { ... };
Is there any specific reason to do so? After all, it seems to work well without that extra burden. Any specific example where I could get problems with my version?
The template parameter is needed because boost::operator actually provides an in-class implementation of the required operators, instead of just making them visible through ADL as in your approach (which is indeed quite clever, if I can say so). I can see the following differences between the two approaches: 1) granularity: well, that's not really an issue, because you could easily elaborate your approach to avoid defining unwanted operators (for example: dont' define operator> if it's not needed) 2) with your approach you can't define operators which must be member functions, that is operator[] and operator-> (that's not too bad, anyway) 3) type deduction rules are different, because boost operators are regular functions, while your operators are function templates. Consider this: struct base : /* EITHER boost::equality_comparable<base> OR rel::ops HERE */ { // ... friend bool operator==(const base&, const base&); }; struct derived : base { }; void test() { base b1, b2; derived d1, d2; b1 != b2; // ok in both approaches d1 != d2; // ok in both approaches b1 != d1; // ok with boost: d1 is implicitly converted to const base& // illegal with rel::ops: can't deduce template argument } This is not terribly bad, but it's enough to make me accept the annoyance of duplicating the class name. But that's just my opinion ;) Also you have to consider there are still a lot of compilers around that don't support ADL correctly, so, despite the formal correctness, there might be some hidden portability problems. Ganesh

Christopher Eltschka <celtschk@web.de> writes:
David Abrahams schrieb:
Christopher Eltschka <celtschk@web.de> writes:
Hi,
I have a very simple header to simplify use of std::rel_ops with user defined classes
std::rel_ops is absolutely useless.
Why?
Because ADL will never find the operators for any type. -- Dave Abrahams Boost Consulting www.boost-consulting.com

David Abrahams wrote:
Because ADL will never find the operators for any type.
Thinking on the fly ... Does this mean we want to add an empty base-class to std::relops, that allows user defined types to work by deriving from this tag-class? Thinking more in terms of WG21 than Boost here, so if the answer is more than a simple Yes or No, I suggest we take the discussion elsewhere <g> -- AlisdairM

On 7/17/06, David Abrahams <dave@boost-consulting.com> wrote:
Christopher Eltschka <celtschk@web.de> writes:
David Abrahams schrieb:
Christopher Eltschka <celtschk@web.de> writes:
std::rel_ops is absolutely useless.
Why?
Because ADL will never find the operators for any type.
The idea is that you use 'using namespace std::rel_ops;' but then you're giving operator!=, >, <=, and >= to all classes in scope (which, arguably, is not as bad thing). Kevin Spinar

"Kevin Spinar" <spinarkm@gmail.com> writes:
On 7/17/06, David Abrahams <dave@boost-consulting.com> wrote:
Christopher Eltschka <celtschk@web.de> writes:
David Abrahams schrieb:
Christopher Eltschka <celtschk@web.de> writes:
std::rel_ops is absolutely useless.
Why?
Because ADL will never find the operators for any type.
The idea is that you use 'using namespace std::rel_ops;' but then you're giving operator!=, >, <=, and >= to all classes in scope (which, arguably, is not as bad thing).
Yeah, but you don't need them in the current scope; you need them in the scope of some generic algorithm you'll pass instances of the classes to. -- Dave Abrahams Boost Consulting www.boost-consulting.com

David Abrahams schrieb:
Christopher Eltschka <celtschk@web.de> writes:
David Abrahams schrieb:
Christopher Eltschka <celtschk@web.de> writes:
Hi,
I have a very simple header to simplify use of std::rel_ops with user defined classes std::rel_ops is absolutely useless.
Why?
Because ADL will never find the operators for any type.
Did you read my header, enclosed with my original mail, together with my explanation? And if so, could you please tell me what's wrong with it? Because IMHO it achieves exactly what you just claimed to be impossible. It apparently works quite well with g++ (gcc version 4.0.2 20050901 (prerelease) (SUSE Linux)), and I don't see why it shouldn't.

Christopher Eltschka <celtschk@web.de> writes:
David Abrahams schrieb:
Christopher Eltschka <celtschk@web.de> writes:
David Abrahams schrieb:
Christopher Eltschka <celtschk@web.de> writes:
Hi,
I have a very simple header to simplify use of std::rel_ops with user defined classes std::rel_ops is absolutely useless.
Why?
Because ADL will never find the operators for any type.
Did you read my header, enclosed with my original mail, together with my explanation? And if so, could you please tell me what's wrong with it? Because IMHO it achieves exactly what you just claimed to be impossible. It apparently works quite well with g++ (gcc version 4.0.2 20050901 (prerelease) (SUSE Linux)), and I don't see why it shouldn't.
I take it back; your scheme works. -- Dave Abrahams Boost Consulting www.boost-consulting.com
participants (6)
-
Alberto Ganesh Barbati
-
AlisdairM
-
Christopher Eltschka
-
David Abrahams
-
Kevin Spinar
-
Robert Ramey