0

I know how to pass a regular comparison class or function to set::set<>.

I am writing some test code and I want to emulate some C libraries using STL's std::set and I want to be able to pass a C callback to the comparison object so a different comparison takes place.

I have the following theoretical code:

struct MyClass
{
    int a;
};

typedef bool (*user_callback_t)(void *, void *);
class MyComparison
{
private:
    user_callback_t cb = nullptr;
public:
    MyComparison(user_callback_t cb): cb(cb) { }
    MyComparison() {}
    bool operator()(const MyClass &a, const MyClass &b) const
    {
        return cb((void *)&a, (void *)&b);
    }
};

int f1()
{
    auto cmp = [](void *a, void *b) -> bool
    {
        return *(int *)a < *(int *)b;
    };

    MyComparison mycmp(cmp);

    std::set<MyClass, MyComparison> m1;

    m1.insert({ 1 });
    m1.insert({ 2 });
    m1.insert({ 3 });

    return 0;
};

Now notice how I can do:

   std::set<MyClass, MyComparison> m1;

But I cannot, somehow, instantiate a MyComparison object, pass it "cmp" and then use that initialize comparison object with that specific set.

Is there a way to achieve that?

Pang
  • 9,564
  • 146
  • 81
  • 122
elias
  • 19
  • 1
  • Why can't you? Btw, you're comparing integers when you have a container of `MyClass`. That's UB. – krzaq Oct 05 '16 at 22:48
  • @krzaq See first answer of http://stackoverflow.com/questions/7160901/why-is-c11s-pod-standard-layout-definition-the-way-it-is - but yes, why can't you? – user253751 Oct 05 '16 at 22:51
  • 1
    Maybe [read some docs](http://en.cppreference.com/w/cpp/container/set/set). – juanchopanza Oct 05 '16 at 23:00
  • That answer is about casting pointer to T to a pointer to U and back to T. That's well-defined indeed. Dereferencing it `MyClass*` as `int*` is undefined as far as my understanding of [basic.lval]/10 from N4140 goes. – krzaq Oct 05 '16 at 23:01
  • According to the manual you can pass the comparator to the `std::set` constructor: http://en.cppreference.com/w/cpp/container/set/set – Galik Oct 05 '16 at 23:01
  • Why is everybody beating on this question so much? Ordinarily you don't have to pass anything to the constructor, so it's not intuitively obvious that that's the solution to the question. Just answer it instead of making snide remarks! – Mark Ransom Oct 05 '16 at 23:06

1 Answers1

3

Your assertion that you can't pass a MyComparison instance to std::set for it to use it is wrong. There is a constructor for std::set expecting just that (in C++11):

explicit set( const Compare& comp = Compare(),
              const Allocator& alloc = Allocator() );

So your MyComparison can be passed as first argument.

std::set<MyClass, MyComparison> m1(mycmp);

If you don't have C++11 or newer available, then this constructor overload is non-existing and you need to use another one:

template< class InputIt >
set( InputIt first, InputIt last,
     const Compare& comp = Compare(),
     const Allocator& alloc = Allocator() );

This one however expects an interator range in the first two arguments. Since we don't actually want to construct from a range, they need to be dummies. You could do someting like:

std::vector<int> dummy;
std::set<MyClass, MyComparison> m1(dummy.begin(), dummy.end(), mycomp);

dummy will not be used afterwards. I am not sure whether there is a nicer solution besides implementing a dummy iterator class.

See http://en.cppreference.com/w/cpp/container/set/set for full reference on constructors of std::set.