25

I have a struct which has a unique key. I want to insert instances of these structs into a set. I know that to do this the < operator has to be overloaded so that set can make a comparison in order to do the insertion.

The following does not work:

#include <iostream>
#include <set>
using namespace std;
struct foo 
{
      int key;
};

bool operator<(const foo& lhs, const foo& rhs)
{
      return lhs.key < rhs.key;
}

set<foo> bar;

int main()
{
    foo *test = new foo;
    test->key = 0;
    bar.insert(test);
}
node ninja
  • 31,796
  • 59
  • 166
  • 254
  • 6
    test.cpp:20: error: no matching function for call to ‘std::set, std::allocator >::insert(foo*&)’ Error seems pretty clear to me. Your trying to put a pointer where an object should be. – hiddensunset4 Apr 28 '11 at 10:00
  • Being able to edit a question is nice but you shouldn't change its meaning doing so. This invalidates any (possibly correct) answer and forces future readers to look at the edit first. There is no limitation on StackOverflow regarding a same person posting several questions within a short amount of time. Actually, that's even a good idea. – ereOn Apr 28 '11 at 11:54

7 Answers7

26

This might help:

struct foo
{
  int key;
};

inline bool operator<(const foo& lhs, const foo& rhs)
{
  return lhs.key < rhs.key;
}

If you are using namespaces, it is a good practice to declare the operator<() function in the same namespace.


For the sake of completeness after your edit, and as other have pointed out, you are trying to add a foo* where a foo is expected.

If you really want to deal with pointers, you may wrap the foo* into a smart pointer class (auto_ptr, shared_ptr, ...).

But note that in both case, you loose the benefit of the overloaded operator< which operates on foo, not on foo*.

ereOn
  • 53,676
  • 39
  • 161
  • 238
  • That works but not when inserting into a set. I changed my question to include the code. – node ninja Apr 28 '11 at 09:54
  • 3
    This answers the original question, I suggest it be chosen as correct, the adapted question was simply a syntax error. – hiddensunset4 Apr 28 '11 at 10:02
  • I loved the fact that you used the keyword [`inline`](http://www.cprogramming.com/tutorial/lesson13.html) in your functions, which boosts up performance! +1 – AK_ Aug 22 '14 at 05:17
  • @AK_ Actually it has little to do with performance but rather with allowing this definition in the header file, in regards to the One Definition Rule. I expect any decent compiler to inline this with or without the `inline` directive. – ereOn Aug 22 '14 at 07:32
  • @ereOn I agree with what you stated but I think performance is also one of the main goals behind `inline` – AK_ Aug 22 '14 at 16:07
  • 1
    @AK_: It was at some point but I don't believe that statement holds with modern compilers which are free to inline or not regardless of the `inline` directive. – ereOn Aug 22 '14 at 18:53
  • I have a question, why does this operator overloading only works when defined outside struct? When I defined it inside it wasnt working, throwing error '<' operator not defined?? – Milind Prajapat Feb 06 '22 at 08:12
9
struct Blah
{
    int x;
};

bool operator<(const Blah &a, const Blah &b)
{
    return a.x < b.x;
}

...

std::set<Blah> my_set;

However, I don't like overloading operator< unless it makes intuitive sense (does it really make sense to say that one Blah is "less than" another Blah?). If not, I usually provide a custom comparator function instead:

bool compareBlahs(const Blah &a, const Blah &b)
{
    return a.x < b.x;
}

...

std::set<Blah,compareBlahs> my_set;
Oliver Charlesworth
  • 267,707
  • 33
  • 569
  • 680
  • 1
    I like the explicit compare function, but it did not compile for me. This is what I did instead https://stackoverflow.com/a/43187783/4876817 – Petr Nov 20 '20 at 13:15
7

You can overload the operator < inside the class also as,

struct foo 
{
  int key;
  bool operator < (const foo &other) const { return key < other.key; }
};

In your question, if you want to use set<foo> bar; as declaration then, you should insert value as,

bar.insert(*test);

But that won't be a good idea, as you are making redundant copy.

iammilind
  • 68,093
  • 33
  • 169
  • 336
2

see ereOn's answer, it's right.

The real problem in you code is this:

foo *test = new foo;
test->key = 0;
bar.insert(test);

You insert a pointer in the set, not a struct. Change the insert to:

 bar.insert( *test );
 //          ^

EDIT: but then you'll need to delete foo, as it will be copied in the set. Or just create it on the stack (using set with pointers is not a good idea, because the arrangement will be "strange" - the set will sort according to the pointers' addresses )

Kiril Kirov
  • 37,467
  • 22
  • 115
  • 187
2

The best thing to do is to give foo a constructor:

struct foo
{
    foo(int k) : key(k) { }
    int key;
};

Then to add, rather than...

foo *test = new foo;
test->key = 0;
bar.insert(test);   // BROKEN - need to dereference ala *test
// WARNING: need to delete foo sometime...

...you can simply use:

bar.insert(foo(0));
Tony Delroy
  • 102,968
  • 15
  • 177
  • 252
1

In c++11 we can use a lambda expression, I used this way which is similar to what @Oliver was given.

#include <set>
#include <iostream>
#include <algorithm>

struct Blah
{
    int x;
};



int main(){

    auto cmp_blah = [](Blah lhs, Blah rhs) { return lhs.x < rhs.x;};

    std::set<Blah, decltype(cmp_blah)> my_set(cmp_blah);

    Blah b1 = {2};
    Blah b2 = {2};
    Blah b3 = {3};

    my_set.insert(b1);
    my_set.insert(b2);
    my_set.insert(b3);

    for(auto const& bi : my_set){
        std::cout<< bi.x << std::endl;
    }

}

demo

GPrathap
  • 7,336
  • 7
  • 65
  • 83
1

The problem isn't in your set; it's in your test object. You're using Java style there. In C++, we just write:

set<foo> bar;

int main()
{
    foo test; // Local variable, goes out of scope at }
    test.key = 0;
    bar.insert(test); // Insert _a copy of test_ in bar.
}
MSalters
  • 173,980
  • 10
  • 155
  • 350