192

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?

Omry Yadan
  • 31,280
  • 18
  • 64
  • 87

7 Answers7

322

1. Modern C++20 solution

auto cmp = [](int a, int b) { return ... };
std::set<int, decltype(cmp)> s;

We use lambda function as comparator. As usual, comparator should return boolean value, indicating whether the element passed as first argument is considered to go before the second in the specific strict weak ordering it defines.

Online demo

2. Modern C++11 solution

auto cmp = [](int a, int b) { return ... };
std::set<int, decltype(cmp)> s(cmp);

Before C++20 we need to pass lambda as argument to set constructor

Online demo

3. Similar to first solution, but with function instead of lambda

Make comparator as usual boolean function

bool cmp(int a, int b) {
    return ...;
}

Then use it, either this way:

std::set<int, decltype(cmp)*> s(cmp);

Online demo

or this way:

std::set<int, decltype(&cmp)> s(&cmp);

Online demo

4. Old solution using struct with () operator

struct cmp {
    bool operator() (int a, int b) const {
        return ...
    }
};

// ...
// later
std::set<int, cmp> s;

Online demo

5. Alternative solution: create struct from boolean function

Take boolean function

bool cmp(int a, int b) {
    return ...;
}

And make struct from it using std::integral_constant

#include <type_traits>
using Cmp = std::integral_constant<decltype(&cmp), &cmp>;

Finally, use the struct as comparator

std::set<X, Cmp> set;

Online demo

diralik
  • 6,391
  • 3
  • 28
  • 52
  • 4
    In example 1, does cmp need to be passed into the constructor? Will the set construct one itself as the lambda type is given as a template type? – PeteUK Jan 10 '20 at 16:18
  • 3
    @PeteUK before C++20 comparator has to be passed into the constructor. In C++20 constructor without arguments can be used. Thank you for question; answer was updated – diralik Jan 11 '20 at 19:39
  • 1
    @diralik Thank you very much for the response and update to your already great answer. – PeteUK Jan 11 '20 at 23:40
  • 5
    That 5. is insane. One finds new nooks and crannies of the language every day. – Jan Hošek Jul 15 '20 at 13:04
  • 1
    Promoted to the accepted answer (just 10+ years after the original question was asked!). – Omry Yadan Dec 24 '20 at 04:47
  • Just an addition to the answer, If we want to pass some set with custom comparator then it is better to use the solution based on comparator as function instead of lambdas. – Nipul Sindwani Feb 07 '21 at 12:41
  • Is there something similar to the c++20 or c+11 solution for unordered_sets ? – Karmah24 Feb 12 '21 at 10:38
  • @Karmah24 Haha. Why would there be? It's unordered. But what you're probably looking for are the hash function and the key equality function. See here: https://en.cppreference.com/w/cpp/container/unordered_set – Andrew Mar 11 '21 at 07:14
  • How can I do the solution number tree for a vector of sets? what will happen to s(&cmp) ? – Mahta Shafieesabet Jul 14 '21 at 06:46
  • @MahtaShafieesabet you need to [pass comparator as constructor parameter](https://godbolt.org/z/vc3dsbnzT) when adding elements to vector. But better to use C++20 solution – diralik Jul 16 '21 at 08:36
185

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);
YLJ
  • 2,940
  • 2
  • 18
  • 29
Yacoby
  • 54,544
  • 15
  • 116
  • 120
  • actually my problem appeared to be an extra closing > in the declaration of the set. I am closing the question as bogus. (using a straight function instead of a functor is perfectly okay for STL) – Omry Yadan Apr 12 '10 at 09:14
  • the code in the question is simpler than what you proposed (for a simple function comparator) and works just fine. – Omry Yadan Apr 12 '10 at 09:20
  • 4
    @Omry: I'd be interested in knowing what compiler you're using: http://codepad.org/IprafuVf –  Apr 12 '10 at 09:21
  • 1
    @Omry Which compiler are you using? –  Apr 12 '10 at 09:22
  • 5
    @Omry The C++ standard says that the second template parameter must be the name of a type - a function name is not the name of a type. –  Apr 12 '10 at 09:24
  • @anon: That's not what the (C++03) Standard says. It says: [23.1.2/2] : " [...] This comparison object may be a pointer to function or an object of a type with an appropriate function call operator." – John Dibling Feb 12 '13 at 13:57
  • I'm getting an error: ...lib/c++/v1/__tree:1596:17: No matching function for call to object of type 'value_compare' (aka 'lex_compare'). Any ideas? – docchang Dec 24 '13 at 09:26
  • If s is a class member and lex_compare is a function how should s been declared? – Shaobo Zi Sep 28 '15 at 03:26
  • 6
    can we use decltype(lex_compare) to denote the function type ? – Lewis Chan Jul 05 '18 at 04:13
  • 2
    @LewisChan correct term would be `std::set s(&lex_compare)` – Nishant Singh Oct 26 '18 at 06:53
18

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!

Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
  • 19
    A matter of opinion, I guess. –  Apr 12 '10 at 11:15
  • 1
    Wow indeed! But it also points to how ghastly C++ syntax is. I hope future versions and standards will simplify C++ syntax. – Nav Dec 10 '20 at 13:02
  • 1
    @Nav The `::t` part can be eliminated in C++14. I think it looks about as good as the Java generics equivalent, while guaranteeing zero runtime overhead. – Potatoswatter Dec 10 '20 at 13:15
9

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.

Tom Whittock
  • 4,081
  • 19
  • 24
8

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.

Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985
2

Hope this will save some of your time. The simple theory you want to know about comparator,

In C++, comparator should return false if its arguments are equal

Chethaka Uduwarage
  • 1,527
  • 2
  • 12
  • 12
1
struct Cmp{
    bool operator()(pair<int,int> i1,pair<int,int> i2){
        return (i1.first < i2.first) || ((i1.first == i2.first) && i1.second >i2.second  );
    }
};
set<pair<int,int> ,Cmp> st;

Sample Program for demo::

// Online C++ compiler to run C++ program online
#include <iostream>
#include<set>
using namespace std;
int arr[50];

struct Cmp{
    bool operator()(pair<int,int> i1,pair<int,int> i2){
        return (i1.first < i2.first) || ((i1.first == i2.first) && i1.second >i2.second  );
    }
};
set<pair<int,int> ,Cmp> st;

int main() {
    // Write C++ code here
    arr[0] = 0,arr[1] = 2,arr[2] = 4,arr[3] = 3;
    st.insert({0,1});
    st.insert({1,2});
    st.insert({2,2});
    st.insert({2,4});
    set<pair<int,int>,Cmp> ::iterator itr;
    for(itr = st.begin();itr!=st.end();itr++ ){
        cout<<" First: " << itr->first <<" second: " <<itr->second<<endl;
    }
    std::cout << "Hello world!";

    return 0;
}

Output of Program::

First: 0 second: 1
 First: 1 second: 2
 First: 2 second: 4
 First: 2 second: 2
Hello world!