[c++] Using custom std::set comparator

I am trying to change the default order of the items in a set of integers to be lexicographic instead of numeric, and I can't get the following to compile with g++:

file.cpp:

bool lex_compare(const int64_t &a, const int64_t &b) 
{
    stringstream s1,s2;
    s1 << a;
    s2 << b;
    return s1.str() < s2.str();
}

void foo()
{
    set<int64_t, lex_compare> s;
    s.insert(1);
    ...
}

I get the following error:

error: type/value mismatch at argument 2 in template parameter list for ‘template<class _Key, class _Compare, class _Alloc> class std::set’
error:   expected a type, got ‘lex_compare’

what am I doing wrong?

This question is related to c++ stl

The answer is


std::less<> when using custom classes with operator<

If you are dealing with a set of your custom class that has operator< defined, then you can just use std::less<>.

As mentioned at http://en.cppreference.com/w/cpp/container/set/find C++14 has added two new find APIs:

template< class K > iterator find( const K& x );
template< class K > const_iterator find( const K& x ) const;

which allow you to do:

main.cpp

#include <cassert>
#include <set>

class Point {
    public:
        // Note that there is _no_ conversion constructor,
        // everything is done at the template level without
        // intermediate object creation.
        //Point(int x) : x(x) {}
        Point(int x, int y) : x(x), y(y) {}
        int x;
        int y;
};
bool operator<(const Point& c, int x) { return c.x < x; }
bool operator<(int x, const Point& c) { return x < c.x; }
bool operator<(const Point& c, const Point& d) {
    return c.x < d;
}

int main() {
    std::set<Point, std::less<>> s;
    s.insert(Point(1, -1));
    s.insert(Point(2, -2));
    s.insert(Point(0,  0));
    s.insert(Point(3, -3));
    assert(s.find(0)->y ==  0);
    assert(s.find(1)->y == -1);
    assert(s.find(2)->y == -2);
    assert(s.find(3)->y == -3);
    // Ignore 1234, find 1.
    assert(s.find(Point(1, 1234))->y == -1);
}

Compile and run:

g++ -std=c++14 -Wall -Wextra -pedantic -o main.out main.cpp
./main.out

More info about std::less<> can be found at: What are transparent comparators?

Tested on Ubuntu 16.10, g++ 6.2.0.


You can use a function comparator without wrapping it like so:

bool comparator(const MyType &lhs, const MyType &rhs)
{
    return [...];
}

std::set<MyType, bool(*)(const MyType&, const MyType&)> mySet(&comparator);

which is irritating to type out every time you need a set of that type, and can cause issues if you don't create all sets with the same comparator.


You are using a function where as you should use a functor (a class that overloads the () operator so it can be called like a function).

struct lex_compare {
    bool operator() (const int64_t& lhs, const int64_t& rhs) const {
        stringstream s1, s2;
        s1 << lhs;
        s2 << rhs;
        return s1.str() < s2.str();
    }
};

You then use the class name as the type parameter

set<int64_t, lex_compare> s;

If you want to avoid the functor boilerplate code you can also use a function pointer (assuming lex_compare is a function).

set<int64_t, bool(*)(const int64_t& lhs, const int64_t& rhs)> s(&lex_compare);

Yacoby's answer inspires me to write an adaptor for encapsulating the functor boilerplate.

template< class T, bool (*comp)( T const &, T const & ) >
class set_funcomp {
    struct ftor {
        bool operator()( T const &l, T const &r )
            { return comp( l, r ); }
    };
public:
    typedef std::set< T, ftor > t;
};

// usage

bool my_comparison( foo const &l, foo const &r );
set_funcomp< foo, my_comparison >::t boo; // just the way you want it!

Wow, I think that was worth the trouble!