I\'m wondering why I can\'t use STL maps with user-defined classes. When I compile the code below, I get the following cryptic error message. What does it mean? Also, why is
Keys must be comparable, but you haven't defined a suitable operator<
for your custom class.
class key
{
int m_value;
public:
bool operator<(const key& src)const
{
return (this->m_value < src.m_value);
}
};
int main()
{
key key1;
key key2;
map<key,int> mymap;
mymap.insert(pair<key,int>(key1,100));
mymap.insert(pair<key,int>(key2,200));
map<key,int>::iterator iter=mymap.begin();
for(;iter!=mymap.end();++iter)
{
cout<<iter->second<<endl;
}
}
By default std::map (and std::set) use operator< to determine sorting. Therefore, you need to define operator<
on your class.
Two objects are deemed equivalent if !(a < b) && !(b < a)
.
If, for some reason, you'd like to use a different comparator, the third template argument of the map
can be changed, to std::greater, for example.
You don't have to define operator<
for your class, actually. You can also make a comparator function object class for it, and use that to specialize std::map
. To extend your example:
struct Class1Compare
{
bool operator() (const Class1& lhs, const Class1& rhs) const
{
return lhs.id < rhs.id;
}
};
std::map<Class1, int, Class1Compare> c2int;
It just so happens that the default for the third template parameter of std::map
is std::less, which will delegate to operator<
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<
on your class just for that. If that's the case, you can use the above trick.
Yet another way to achieve the same is to specialize std::less
:
namespace std
{
template<> struct less<Class1>
{
bool operator() (const Class1& lhs, const Class1& rhs) const
{
return lhs.id < rhs.id;
}
};
}
The advantage of this is that it will be picked by std::map
"by default", and yet you do not expose operator<
to client code otherwise.
You need to define operator <
for the Class1.
Map needs to compare the values using operator < and hence you need to provide the same when user defined class are used as key.
class Class1
{
public:
Class1(int id);
bool operator <(const Class1& rhs) const
{
return id < rhs.id;
}
private:
int id;
};
I'd like to expand a little bit on Pavel Minaev's answer, which you should read before reading my answer. Both solutions presented by Pavel won't compile if the member to be compared (such as id
in the question's code) is private. In this case, VS2013 throws the following error for me:
error C2248: 'Class1::id' : cannot access private member declared in class 'Class1'
As mentioned by SkyWalker in the comments on Pavel's answer, using a friend
declaration helps. If you wonder about the correct syntax, here it is:
class Class1
{
public:
Class1(int id) : id(id) {}
private:
int id;
friend struct Class1Compare; // Use this for Pavel's first solution.
friend struct std::less<Class1>; // Use this for Pavel's second solution.
};
Code on Ideone
However, if you have an access function for your private member, for example getId()
for id
, as follows:
class Class1
{
public:
Class1(int id) : id(id) {}
int getId() const { return id; }
private:
int id;
};
then you can use it instead of a friend
declaration (i.e. you compare lhs.getId() < rhs.getId()
).
Since C++11, you can also use a lambda expression for Pavel's first solution instead of defining a comparator function object class.
Putting everything together, the code could be writtem as follows:
auto comp = [](const Class1& lhs, const Class1& rhs){ return lhs.getId() < rhs.getId(); };
std::map<Class1, int, decltype(comp)> c2int(comp);
Code on Ideone