views:

201

answers:

7

Hi!

I have a struct for which i want to define a relative order by defining < , > , <= and >= operators. actually in my order there won't be any equality, so if one struct is not smaller than another, it's automatically larger.

I defined the first operator like this:

struct MyStruct{
...
...

bool operator < (const MyStruct &b) const {return (somefancycomputation);}

};

now i'd like to define the other operators based on this operator, such that <= will return the same as < and the other two will simply return the oposite. so for example for the > operator i'd like to write something like

bool operator > (const MyStruct &b) const {return !(self<b);}

but i don't know how to refere to this 'self' since i can refere only to the fields inside the current struct.

whole is in C++

hope my question was understandable :)

thank you for the help!

+9  A: 

Self is *this.

That is, this is a pointer to the current object, so you need to dereference it to get the actual object.

Dave Hinton
@genesys Expanding on Dave's point, You'll want to write your code as something like "bool operator > (const MyStruct }"
Glen
Upvoted. Another possibility is to make a function that compares two given structs A and B.
mcandre
+1  A: 

Overloaded operator is just a member function though with a fancy syntax. You can explicitly call it like this:


this->operator<( b );

or save yourself some hair :) and just provide int compare( const MyStruct& ) member function and use that in all the non-member comparison operators.

Nikolai N Fetissov
Yes, providing a named function and reusing it is a much more readable approach.
sharptooth
A: 

this is a pointer to your current object. So (as @Dave Hinton says) you'd have to dereference it. But, theres another option

Two options:

return !(*this<b)

or

return (!this->operator<(b))

Yes, the first one is quite nicer.

Tom
A: 

actually in my order there won't be any equality, so if one struct is not smaller than another, it's automatically larger.

That's a problem. C++ operations (and many algorithms) require a strict weak ordering, which (among others) implies that x == x holds for any x. More generally:

not (x < y) and not (y < x)

implies

x == y

for any x and y. In other words: you most probably must define some kind of equality for your struct to work with any conventional algorithm.

Konrad Rudolph
Lots of things will still work, though, with some provisos. `std::sort` will work, although naturally you have no control of the orders of "equivalent" values. You can still have `set` and `map`, but natutally, once you have a value a collection you can't place an "equivalent" entry in the collection (but you could with `multiset` and `multimap`). Things like `find` and `equal_range` are doomed.
Charles Bailey
A string *weak* ordering, doesn't imply a total order, or equivalence (w.r.t the order) doesn't imply equality., or in other words, `not (x < y) and not (y < x)` does not necessarily imply that `x == y`. It must hold that `not (x < x)`, though.
Charles Bailey
@Charles, doesn't "weak" mean that two elements may be equivalent with regard to the ordering? I think what you talked about is called "partial ordering" not "weak ordering" ?
Johannes Schaub - litb
Charles Bailey
I think that partial orders partition sets into chains which could be extended to a full order if you linked them end to end in some arbitrary fashion, and strict weak orders (or total preorders) partition sets into a chain of equivalence sets which could be extended to a total order if you imposed an arbitrary order on each equivalence set.
Charles Bailey
@Charles: no, you can use boolean algebra to derive my statement from the rules of antisymmetry and reflexivity, which define a strict weak ordering.
Konrad Rudolph
What exactly do you mean by `==` then? `¬(x < y) ^ ¬(y < x)` must be an equivalence relationship in a strict weak ordering, but that doesn't imply that `x` and `y` are the same element. A strict weak ordering is irreflexive, not reflexive. I think we may be using different definitions for something important.
Charles Bailey
@Charles: oops, I actually meant *irreflexivity* in my previous comment, not *reflexivity*. By `==` I mean the equivalence relation, not identity. Perhaps that's where our confusion stems from. But as I see it, (almost?) all programming languages differentiate between equality (= equivalence) and identity (= same memory location, pointer equality …).
Konrad Rudolph
I think so. The point that I was trying to make was that `operator==` is not used by `set`, `map`, `sort`, etc. so it does not matter if it is the same as `!(a < b) }` and reserve `==` for equality of doubles. It can still be used in standard algorithms requiring an appropriate `less` comparison.
Charles Bailey
@Charles: point taken and comment worth making. However, I mainly objected to this part of the quote: “if one struct is not smaller than another, it's automatically larger” which may result in surprises for the OP. Or it might not, depending on usage.
Konrad Rudolph
+6  A: 

If you are providing an operator< with all the appropriate logic then (whether or not it is implemented as a free function) you can implement the other operators in terms of it as free functions. This follows the rule of preferring non-members over members where possible and free functions will have identical behaviour w.r.t. conversions of the left and right operands, whereas operators implemented as member functions don't.

e.g.

inline bool operator>(const MyStruct& a, const MyStruct&b)
{
    return b < a;
}

inline bool operator<=(const MyStruct& a, const MyStruct&b)
{
    return !(b < a);
}

inline bool operator>=(const MyStruct& a, const MyStruct&b)
{
    return !(a < b);
}
Charles Bailey
Another posting I'd like to have 10 votes to give to... And I'd like to add that `operator<`, being a binary operator that treats both of its arguments the same (it leaves them alone), usually should be implemented as a non-member, too.
sbi
thank you for your answer..I'm sorry for the noob question, but could you also explain me what exaclty is the difference of non-members and members and why to prefere the first one?? thanks :)
A: 

I'm with Tom/Dave above in that the direct way to write this:

return !(*this<b);

but for me the most explicitly clear form is:

return !operator<(b);

and Tom's: return !(this->operator<(b)); seems a little silly.

This little bit nasty C preprocessor code (I also have a better but more technical template solution):

#define CREATE_SYMETRIC_ORDINAL_OPERATORS(Type) \
int operator !=(Type X) {return !((*this)==X); } \
int operator <(Type X) {return !((*this)>=X); } \
int operator >=(Type X) {return (((*this)>X)||((*this)==X)); } \
int operator <=(Type X) {return (((*this)<X)||((*this)==X)); }

automatically defines all comparison operators assuming == and > are defined like so:

class Comparable {
  public:
    int operator ==(Comparable B);
    int operator >(Comparable B);
    CREATE_SYMETRIC_OPERATORS(Comparable);
};
Elemental
That set of macros gives me the squicks a bit I'm afraid.
Paul Nathan
Yeah I never liked it either and have been replacing it with a simple inheritance from a class that has == and < defined as pure virtual. Performance is a bit weaker but the readability is better
Elemental
@Elemental: You can get both (simplicity and speed) by using the CRTP. Also, it's probably better to base comparison on `operator<()` as this is the one that's usually (`std::map`, `std::sort` etc.) required. Finally, IMO it's better to take the RHS argument per const reference, as the optimizer might fail to prevent the copying of an expensive to copy type. Since this is too big for a comment, I'm going to add my own answer showing this.
sbi
@Elemental: Oh, and comparison should return `bool`, not `int`!
sbi
Elemental
@Elemental: Up to now it missed a few `const`, though -- as does yours. `:)` Well, I'm not convinced by the idea anyway. First, for the reason given by AProgrammer I strongly prefer those operators as free functions. Second, the machinery driving this is complex enough to probably produce quite funny error messages if you do something wrong, while it only saves writing four small and dumb inline functions -- at least according to my coding style that's four lines of code.
sbi
+1  A: 

This is meant as a slight improvement over Element's answer:

template< class Derived >
class Comparable {
  public:
    bool operator !=(const Derived& rhs) const
    {return !( static_cast<Derived&>(*this) == rhs ); }

    bool operator <(const Derived& rhs) const
    {return rhs < static_cast<Derived&>(*this); } 

    bool operator >=(const Derived& rhs) const
    {return !( static_cast<Derived&>(*this) < rhs ); }

    bool operator <=(const Derived& rhs) const
    {return !( static_cast<Derived&>(*this) > rhs ); }
};

struct MyStruct : public Comparable<MyStruct> {
    bool operator ==(const MyStruct & rhs) const
    {return /* whatever */; }

    bool operator <(const MyStruct & rhs) const
    {return /* whatever */; }
};
sbi