R@M3$H.NBlog

STL maps with user-defined objects

15 October, 2013 - 3 min read

#include <iostream>
#include <map>

using namespace std;

class A
{
public:
    A(int id): id(id)
    { };

private:
    int id;
};

int main()
{
    A a(1) ;

    map< A, int> m ;
    m[a] = 123 ;

    return 0;
}

 

The above code throws the error:

stl_function.h:237:22: error: no match for 'operator<' in '__x < __y'

 

Method-1: (Overload the operator &lt; )

You need to define operator < for the class A. Because Keys must be comparable.

The only requirement is that Compare (which defaults to less&lt;Key&gt;, which defaults to using operator&lt; to compare keys) must be a "strict weak ordering".

Map needs to compare the values using operator < and hence you need to provide the same when user defined class are used as key.

    bool operator &lt;(const A&amp; rhs) 
    {
        return id &lt; rhs.id;
    }

The above code throws the error:

stl_function.h:237:22: error: passing 'const A' as 'this' argument of 'bool A::operator<(const A&)' discards qualifiers [-fpermissive]

 

The stl container map is an ordered collection of pairs, and the
default ordering is done using the function object less(). This
function object defines operator() by taking two const references and
compares them using corresponding operator<. Hence, when you invoke
"find"/insert/[] on m, the less() function eventually calls Point::operator<()
and the arguments that it passes are const references. Hence
Point::operator<() must be declared const.

    bool operator <(const A& rhs) const
    {
        return id < rhs.id;
    }

 

Method-2: (Providing a functor)

You don't have to define operator&lt; for your class, actually. You can also make a comparator function object class for it, and use that to specialize std::map (the third template argument of the map can be changed). To extend your example:

struct ClassACompare
{
   bool operator() (const A&amp; lhs, const A&amp; rhs)
   {
       return lhs.id &lt; rhs.id;
   }
};

std::map&lt;Class1, int, ClassACompare&gt; c2int;

It just so happens that the default for the third template parameter of std::map is std::less, which will delegate to operator&lt; defined for your class (and fail if there is none). But sometimes you want objects to be usable as map keys, but you do not actually have any meaningful comparison semantics, and so you don't want to confuse people by providing operator&lt; on your class just for that. If that's the case, you can use the above trick.

Method-3: (Specializing std::less for class A)

Yet another way to achieve the same is to specialize std::less:

namespace std
{
    template&lt;&gt; struct less&lt;A&gt;
    {
       bool operator() (const A&amp; lhs, const A&amp; rhs) const
       {
           return lhs.id &lt; rhs.id;
       }
    }
}

The advantage of this is that it will be picked by std::map "by default", and yet you do not expose operator&lt;to client code otherwise.

Note: For Inserting into map use the make_pair:

m.insert ( make_pair<A,int>(obj1, 1)); OR

m.insert ( std::map <A,int>::value_type (obj1,1)) ;

END