Utilizzo del comparatore std :: set personalizzato


106

Sto cercando di modificare l'ordine predefinito degli elementi in un insieme di numeri interi in modo che sia lessicografico anziché numerico e non riesco a compilare quanto segue con 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);
    ...
}

Ottengo il seguente errore:

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

Che cosa sto facendo di sbagliato?

Risposte:


159

Stai usando una funzione in cui dovresti usare un funtore (una classe che sovraccarica l'operatore () in modo che possa essere chiamato come una funzione).

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();
    }
};

Quindi si utilizza il nome della classe come parametro di tipo

set<int64_t, lex_compare> s;

Se vuoi evitare il codice boilerplate del funtore puoi anche usare un puntatore a funzione (assumendo che lex_comparesia una funzione).

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

4
@Omry: sarei interessato a sapere quale compilatore stai usando: codepad.org/IprafuVf

1
@Omry Quale compilatore stai usando?

4
@Omry Lo standard C ++ dice che il secondo parametro del modello deve essere il nome di un tipo - il nome di una funzione non è il nome di un tipo.

6
possiamo usare decltype (lex_compare) per denotare il tipo di funzione?
Lewis Chan

2
Il termine corretto di @LewisChan sarebbestd::set<int64_t, decltype(&lex_compare)> s(&lex_compare)
Nishant Singh

110

1. Moderna soluzione C ++ 20

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

Usiamo la funzione lambda come comparatore. Come al solito, il comparatore dovrebbe restituire un valore booleano, indicando se l'elemento passato come primo argomento è considerato prima del secondo nello specifico ordine rigoroso debole che definisce.

Demo online

2. Moderna soluzione C ++ 11

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

Prima di C ++ 20 dobbiamo passare lambda come argomento per impostare il costruttore

Demo online

3. Simile alla prima soluzione, ma con funzione invece di lambda

Crea comparatore come una normale funzione booleana

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

Quindi usalo, in questo modo:

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

Demo online

o in questo modo:

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

Demo online

4. Vecchia soluzione utilizzando struct con ()operatore

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

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

Demo online

5. Soluzione alternativa: creare una struttura dalla funzione booleana

Prendi la funzione booleana

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

E crea struct da esso usando std::integral_constant

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

Infine, usa la struttura come comparatore

std::set<X, Cmp> set;

Demo online


3
Nell'esempio 1, cmp deve essere passato al costruttore? L'insieme ne costruirà uno da solo poiché il tipo lambda viene fornito come tipo di modello?
PeteUK

2
@PeteUK prima del confronto C ++ 20 deve essere passato al costruttore. In C ++ 20 è possibile utilizzare il costruttore senza argomenti. Grazie per la domanda; la risposta è stata aggiornata
diralik

1
@diralik Grazie mille per la risposta e l'aggiornamento alla tua già ottima risposta.
PeteUK

1
lambda generico sembra funzionare anche per 1 e 2
ZFY

2
Quella 5. è una follia. Ogni giorno si trovano nuovi angoli e fessure della lingua.
Jan Hošek

18

La risposta di Yacoby mi ispira a scrivere un adattatore per incapsulare il boilerplate del funtore.

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, penso che valesse la pena!


17
Una questione di opinione, immagino.

6

Puoi usare un comparatore di funzioni senza avvolgerlo in questo modo:

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

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

il che è irritante da digitare ogni volta che è necessario un set di quel tipo e può causare problemi se non si creano tutti i set con lo stesso comparatore.


3

std::less<> quando si utilizzano classi personalizzate con operator<

Se hai a che fare con un insieme della tua classe personalizzata che è stata operator<definita, puoi semplicemente usare std::less<>.

Come accennato in http://en.cppreference.com/w/cpp/container/set/find C ++ 14 ha aggiunto due nuove findAPI:

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

che ti permettono di fare:

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);
}

Compila ed esegui:

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

Maggiori informazioni su std::less<>possono essere trovate su: Cosa sono i comparatori trasparenti?

Testato su Ubuntu 16.10, g++6.2.0.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.