views:

669

answers:

5
struct Node
{
 int a;
 int b;
};

Node node;
node.a = 2;
node.b = 3;

map<int, int> aa;
aa[1]=1; //O.K.

map<Node, int> bb;
bb[node]=1; //Compile Error

When I tried to map a struct into int, it gave me a compile error. Why? Thank you!

+1  A: 

Could you please post the compiler error - They're intended to tell you, what's wrong.

I guess your error occurs since Node doesn't implement a comparison operator which is required by the map in order to identify it's elements.

Dario
+4  A: 

You have to tell std::map how to compare the Node objects. By default it tries to do so by using the less than operator. But you didn't provide any less than operator for Node. The easiest solution would be to supply one.

Free function example:

bool operator<(Node const& n1, Node const& n2)
{
    return n1.a<n2.a || (n1.a==n2.a && n1.b<n2.b);
}

Note that, for any pair of node objects x,y with !(x<y) and !(y<x) the map will regard x and y as equal (same key).

sellibitze
+9  A: 

For a thing to be usable as a key in a map, you have to be able to compare it using operator<(). You need to add such an operator to your node class:

struct Node
{
 int a;
 int b;

 bool operator<( const Node & n ) const {
   return this->a < n.a;   // for example
 }
};

Of course, what the real operator does depends on what comparison actually means for your struct.

anon
thank you all! I didn't think that map requires comparison.
sevity
@sevity - I'd recommend "The C++ Standard Library" (http://www.josuttis.com/libbook/) which will flatten the learning curve of the C++ library significantly
mloskot
To be precise, you *have* to be able to compare it using `std::less` (or specify some other comparator). Making it comparable with `operator<` is the obvious way to achieve this. But for example pointers are not comparable with `<`, but they can be used as keys in maps with the default comparator.
Steve Jessop
@Steve To be even more precise, pointers MAY not be comparable with < - it depends what they point to.
anon
Yes, what I meant was, "pointers of the same type are not in general comparable with `<`". Pointers to different parts of the same object/array are comparable, and for that matter all pointers may be comparable if the implementation says so.
Steve Jessop
+2  A: 

You need to define less-than operator to enable comparisons for your Node type:

struct Node
{
 int a;
 int b;
};

bool operator<(Node const& n1, Node const& n2)
{  
   // TODO: Specify condition as you need
   return ... ;
}

Here you may check what LessThan Comparable mean for a user-defined type.

Alternative solution is to define a functor based on std::binary_function. From design point of view, this option has advantages because comparison is effectively decoupled from the Node class. This makes it possible to define maps specialised with different comparison conditions (functors).

#include <map>

struct Node
{
 int a;
 int b;
};

struct NodeLessThan
    : public std::binary_function<Node, Node, bool>
{
    bool operator() (Node const& n1, Node const& n2) const
    {
        // TODO: your condition
        return n1.a < n2.a;
    }
};

int main()
{
    Node node;
    node.a = 2;
    node.b = 3;

    typedef std::map<Node, int, NodeLessThan> node_map_t;
    node_map_t bb;
    bb[node] = 1;
}

So, you can define more comparisons than just NodeLessThan, for example using different conditions or one comparing only by Node::a another comparing both components, Node::a and Node::b. Then, defined different types of maps:

typedef std::map<Node, int, NodeLessThan>    node_map_t;
typedef std::map<Node, int, NodeLessThanByA> node_map_a_t;

Such decoupling is less intrusive (does not touch Node class at all) and is beneficial to achieve more extensible solution.

mloskot
+1  A: 

If you don't really need to have your data sorted by key, you can use the new unordered_map:

#include <unordered_map>

... 

std::tr1::unordered_map<Node, int> aa;  // Doesn't require operator<(Node, Node)

You'll need a recent compiler for this to work.

UPDATE As Neil points out you need an specialized hash function if you want an unordered_map with Node keys.

struct NodeHash : std::unary_function<Node, size_t>
{ 
    size_t operator()(Node const & node) const
    {
        return static_cast<size_t>(node.a + 1) * static_cast<size_t>(node.b + 1);
    }
};

And then the map becomes:

 std::tr1::unordered_map<Node, int, NodeHash> aa;

Also, as sellibitze says, an operator== is needed to compare keys in case of hash collision:

bool operator==(const Node & lhs, const Node & rhs)
{
    return lhs.a == rhs.a && rhs.b == rhs.b;
}

So I guess that std::map is much easier to use after all.

Manuel
You are correct - you don't need operator<(). But instead you must provide a hash function, which is typically far harder to write than operator<().
anon
@Neil: Thanks, I've tried to adapt my answer.
Manuel
@Manuel I think you have nicely illustrates how hard it is to write a good hash function :-) Your function will have the the same value (1) for the (a,b) pairs (0,1) and (1,0) and for lots of other pairs too.
anon
Doesn't unordered_map also need operator== for the keys?
sellibitze
@Neil - Yes it's hard indeed. Shouldn't the standard provide a simple way to deal with this? Maybe a function "mem_hash" to which you pass the address of a block and its size in bytes, and which returns a hash for the contents of that block.
Manuel
@sellibitze - You're right, I've added it to my answer.
Manuel
@Neil - Looks like (a+1)*(b+1) works much better as hash function. In my tests ( http://codepad.org/Vvc0DILo ) a tr1::unordered_set equipped with this hash function performs lookup operations twice as fast as an std::set.
Manuel
@Manuel I'm sure it will be fast - but adding 1 to the variables makes no difference to the quality of the function. In this case it doesn't matter much, but for types where the function is expensive to calculate, providing one that gives a good spread of values is essential otherwise the combination of slow hashing speed and collision handling can severely degrade performance.
anon
@Neil Butterworth - I just discovered that the Boost.Hash library has a function hash_combine which does precisely what is needed in this case.
Manuel