N D Thokare
N D Thokare

Reputation: 1743

How to make set:: find() work for custom class objects?

I'm a bit confused about using STL set::find() for a set of my own defined class objects.

My class contains more than two items (3/4/5 etc.), so how can I overload less operator?

I tried for 3 variable, which is as follows and working fine:

return( (a1.i < a2.i) ||
    (!(a1.i > a2.i) && (a1.f < a2.f)) ||
    (!(a1.i > a2.i) && !(a1.f > a2.f) && (a1.c < a2.c)));

where, a1, and a2 are class objects and (i, f and c are class members).

Now I want to generalize this for n members, but my find() does not always work.

I've been looking through STL's detailed documentation, trying to learn how set::find() is implemented, and why it needs less (<) operator overloading.

I referred to sgi and msdn documentation, but I could not find much about implementation details of set::find() there, either.

What am I doing wrong in my set::find() implementation?

Upvotes: 4

Views: 8373

Answers (5)

hc_
hc_

Reputation: 2628

You have to define a strict ordering of your objects. So if your object is made up of n members a_1 .. a_n which all have a strict ordering themselves, what you can do is:

bool operator< (const TYPE &rhs) {
  if (a_1 < rhs.a_1) return true; else if (a_1 > rhs.a_1) return false;
  if (a_2 < rhs.a_2) return true; else if (a_2 > rhs.a_2) return false;
  ...
  if (a_n < rhs.a_n) return true;
  return false;
}

Edit: If either boost or C++11 is an option for you, you should really go with the std::tie/boost::tie method Luc Danton suggests in his answer. It's much cleaner.

Upvotes: 5

Luc Danton
Luc Danton

Reputation: 35449

You can use a tuple to easily get an lexicographical ordering of your members:

return std::tie(lhs.i, lhs.f, lhs.c) < std::tie(rhs.i, rhs.f, rhs.c);

This requires that every member be of a comparable type, e.g. lhs.i < rhs.i makes sense.

Note that std::tie and std::tuple are only available for C++11, so for C++03 you can use e.g. Boost.Tuple which does provide a boost::tie (boost::tuple uses the same ordering as std::tuple).

As to where this should go, it is customary to put that in an operator< (after all this is what make the use of tie for an easy ordering possible in the first place). Quite often this operator will be a friend, so this would look like:

class foo {
public:
    /* public interface goes here */

    // declaration of non-member friend operator
    // if it doesn't need to be a friend, this declaration isn't needed
    friend
    bool operator<(foo const& lhs, foo const& rhs);

private:
    T t;
    U u;
    V v;

};

bool operator<(foo const& lhs, foo const& rhs)
{
    // could be boost::tie
    return std::tie(lhs.t, lhs.u, lhs.v) < std::tie(rhs.t, rhs.u, rhs.v);
}

As you can see it's not fully automatic as the implementation of operator< needs to list every member of foo (or at least those that matter for the ordering), twice. There isn't a better way I'm afraid.

Instead of providing an operator< you can specialize std::less for foo but that's a bit exotic and not the preferred way. If the ordering would still not make sense to be part of the extended interface of foo (e.g. there might be more than one ordering that makes sense without a canonical one), then the preferred way is to write a functor:

struct foo_ordering {
    bool operator()(foo const& lhs, foo const& rhs) const
    {
        /* implementation as before, but access control/friendship
           has to be planned for just like for operator< */
    }
};

Then you'd use e.g. std::set<foo, foo_ordering>.

Be aware that no matter what form the ordering takes (through either operator<, std::less<foo> or a functor) if it is used with an std::set or any other associative container (and by default e.g. std::set<T> uses std::less<T> which in turn uses operator< by default) it must follow some stringent criteria, i.e. it must be a strict weak ordering. However if all the members that are used for the foo ordering themselves have SW orderings then the resulting lexicographical ordering is also a SW ordering.

Upvotes: 7

cybevnm
cybevnm

Reputation: 2566

std::set element comparison function should define Strict Weak Ordering relation on elements domain. Using this definition we can say that two elements are equivalent if compare( a, b ) is false and compare( b, a ) is false too. std::find can be implemented using this assumption.
You can find more here: http://www.sgi.com/tech/stl/set.html and http://www.sgi.com/tech/stl/StrictWeakOrdering.html

Upvotes: 2

Stefan Marinov
Stefan Marinov

Reputation: 581

This is only a partial answer, but a detailed documentation of STL can be found on the website of SGI.

Upvotes: 1

Lol4t0
Lol4t0

Reputation: 12547

Your operator < should be capable to compare every object with given one, like that

struct Data
{
    bool operator < (const Data& right) const
    {
    return( (this.i < right.i) ||
        (!(this.i > right.i) && (this.f < right.f)) ||
        (!(this.i > right.i) && !(this.f > right.f) && (this.c < right.c)));
    }
}

Also, your compare algorithm looks doubtful, because it doees not consider cases, when

this.i == right.i

or

this.f == right.f

And you actually should not be interested in std::set implementation. It can change from compiler to compiler and can be modified in future. Your program should make assumptions only about container interface, never implementation.

Upvotes: 1

Related Questions