Verwenden des benutzerdefinierten std :: set-Komparators

105

Ich versuche, die Standardreihenfolge der Elemente in einer Reihe von Ganzzahlen so zu ändern, dass sie lexikografisch statt numerisch sind, und ich kann Folgendes nicht mit g ++ kompilieren:

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

Ich erhalte folgende Fehlermeldung:

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

Was mache ich falsch?

Omry Yadan
quelle

Antworten:

158

Sie verwenden eine Funktion, bei der Sie wie folgt einen Funktor verwenden sollten (eine Klasse, die den Operator () überlastet, damit er wie eine Funktion aufgerufen werden kann).

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

Anschließend verwenden Sie den Klassennamen als Typparameter

set<int64_t, lex_compare> s;

Wenn Sie den Funktor-Boilerplate-Code vermeiden möchten, können Sie auch einen Funktionszeiger verwenden (vorausgesetzt, es lex_comparehandelt sich um eine Funktion).

set<int64_t, bool(*)(const int64_t& lhs, const int64_t& rhs)> s(&lex_compare);
Yacoby
quelle
4
@Omry: Ich würde gerne wissen, welchen Compiler Sie verwenden: codepad.org/IprafuVf
1
@Omry Welchen Compiler verwenden Sie?
4
@Omry Der C ++ - Standard besagt, dass der zweite Vorlagenparameter der Name eines Typs sein muss - ein Funktionsname ist nicht der Name eines Typs.
6
können wir decltype (lex_compare) verwenden, um den Funktionstyp zu bezeichnen?
Lewis Chan
2
@ LewisChan korrekter Begriff wärestd::set<int64_t, decltype(&lex_compare)> s(&lex_compare)
Nishant Singh
108

1. Moderne C ++ 20-Lösung

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

Wir verwenden die Lambda-Funktion als Komparator. Wie üblich sollte der Komparator einen booleschen Wert zurückgeben, der angibt, ob das als erstes Argument übergebene Element in der von ihm definierten spezifischen strengen schwachen Reihenfolge vor dem zweiten steht .

Online-Demo

2. Moderne C ++ 11-Lösung

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

Vor C ++ 20 müssen wir Lambda als Argument übergeben, um den Konstruktor zu setzen

Online-Demo

3. Ähnlich wie bei der ersten Lösung, jedoch mit Funktion anstelle von Lambda

Komparator wie gewohnt boolesche Funktion machen

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

Dann benutze es entweder so:

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

Online-Demo

oder so:

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

Online-Demo

4. Alte Lösung mit struct mit ()Operator

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

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

Online-Demo

5. Alternative Lösung: Erstellen Sie eine Struktur aus einer Booleschen Funktion

Nehmen Sie die Boolesche Funktion

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

Und machen Sie daraus Struktur mit std::integral_constant

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

Verwenden Sie schließlich die Struktur als Komparator

std::set<X, Cmp> set;

Online-Demo

Diralik
quelle
3
Muss cmp in Beispiel 1 an den Konstruktor übergeben werden? Wird die Menge selbst eine Konstruktion erstellen, da der Lambda-Typ als Vorlagentyp angegeben wird?
PeteUK
2
@PeteUK vor dem C ++ 20-Komparator muss an den Konstruktor übergeben werden. In C ++ 20 kann ein Konstruktor ohne Argumente verwendet werden. Vielen Dank für Ihre Frage. Antwort wurde aktualisiert
Diralik
1
@diralik Vielen Dank für die Antwort und das Update auf Ihre bereits großartige Antwort.
PeteUK
1
Generisches Lambda scheint auch für 1 und 2 zu
funktionieren
2
Das 5. ist verrückt. Man findet jeden Tag neue Ecken und Winkel der Sprache.
Jan Hošek
18

Yacobys Antwort inspiriert mich, einen Adapter zum Einkapseln der Funktor-Boilerplate zu schreiben.

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, ich denke das war die Mühe wert!

Kartoffelklatsche
quelle
17
Eine Ansichtssache, denke ich.
6

Sie können einen Funktionskomparator verwenden, ohne ihn wie folgt zu verpacken:

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

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

Dies ist irritierend, wenn Sie jedes Mal einen Satz dieses Typs benötigen, und kann Probleme verursachen, wenn Sie nicht alle Sätze mit demselben Komparator erstellen.

Tom Whittock
quelle
3

std::less<> bei Verwendung von benutzerdefinierten Klassen mit operator<

Wenn Sie es mit einem Satz Ihrer benutzerdefinierten Klasse zu tun haben, der operator<definiert wurde, können Sie ihn einfach verwenden std::less<>.

Wie unter http://en.cppreference.com/w/cpp/container/set/find erwähnt, hat C ++ 14 zwei neue findAPIs hinzugefügt :

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

mit denen Sie Folgendes tun können:

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

Kompilieren und ausführen:

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

Weitere Informationen std::less<>finden Sie unter: Was sind transparente Komparatoren?

Getestet unter Ubuntu 16.10, g++6.2.0.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
quelle