Menggunakan komparator std :: set kustom

106

Saya mencoba mengubah urutan default item dalam satu set bilangan bulat menjadi leksikografik, bukan numerik, dan saya tidak bisa mendapatkan yang berikut untuk dikompilasi dengan 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);
    ...
}

Saya mendapatkan kesalahan berikut:

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

apa yang saya lakukan salah?

Omry Yadan
sumber

Jawaban:

159

Anda menggunakan fungsi di mana Anda harus menggunakan functor (kelas yang membebani operator () sehingga bisa disebut seperti fungsi).

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

Anda kemudian menggunakan nama kelas sebagai parameter tipe

set<int64_t, lex_compare> s;

Jika Anda ingin menghindari kode boilerplate functor Anda juga dapat menggunakan penunjuk fungsi (dengan asumsi lex_compareadalah sebuah fungsi).

set<int64_t, bool(*)(const int64_t& lhs, const int64_t& rhs)> s(&lex_compare);
Yacoby
sumber
4
@Omry: Saya tertarik untuk mengetahui kompiler apa yang Anda gunakan: codepad.org/IprafuVf
1
@Omry Kompiler mana yang Anda gunakan?
4
@Omry Standar C ++ mengatakan bahwa parameter template kedua harus berupa nama tipe - nama fungsi bukan nama tipe.
6
dapatkah kita menggunakan jenis deklarasi (lex_compare) untuk menunjukkan jenis fungsi?
Lewis Chan
2
@LewisChan istilah yang benar adalahstd::set<int64_t, decltype(&lex_compare)> s(&lex_compare)
Nishant Singh
109

1. Solusi C ++ 20 modern

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

Kami menggunakan fungsi lambda sebagai pembanding. Seperti biasa, pembanding harus mengembalikan nilai boolean, yang menunjukkan apakah elemen yang diteruskan sebagai argumen pertama dianggap berada sebelum argumen kedua dalam urutan lemah ketat spesifik yang ditentukannya .

Demo online

2. Solusi C ++ 11 modern

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

Sebelum C ++ 20 kita perlu meneruskan lambda sebagai argumen untuk menyetel konstruktor

Demo online

3. Mirip dengan solusi pertama, tetapi dengan fungsi bukan lambda

Jadikan pembanding seperti fungsi boolean biasa

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

Kemudian gunakan dengan cara ini:

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

Demo online

atau begini:

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

Demo online

4. Solusi lama menggunakan struct dengan ()operator

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

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

Demo online

5. Solusi alternatif: buat struct dari fungsi boolean

Ambil fungsi boolean

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

Dan buat struct dari itu menggunakan std::integral_constant

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

Terakhir, gunakan struct sebagai pembanding

std::set<X, Cmp> set;

Demo online

diralik
sumber
3
Dalam contoh 1, apakah cmp perlu diteruskan ke konstruktor? Akankah set membuat satu sendiri sebagai jenis lambda diberikan sebagai jenis template?
PeteUK
2
@PeteUK sebelum pembanding C ++ 20 harus diteruskan ke konstruktor. Dalam C ++ 20 konstruktor tanpa argumen dapat digunakan. Terima kasih atas pertanyaannya; jawaban diperbarui
diralik
1
@diralik Terima kasih banyak atas tanggapan dan pembaruan atas jawaban Anda yang sudah bagus.
PeteUK
1
lambda generik tampaknya juga bekerja untuk 1 dan 2
ZFY
2
5. Itu gila. Orang menemukan celah dan celah baru dari bahasa tersebut setiap hari.
Jan Hošek
18

Jawaban Yacoby menginspirasi saya untuk menulis adaptor untuk mengenkapsulasi pelat boiler functor.

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, saya pikir itu sepadan dengan masalahnya!

Potatoswatter
sumber
17
Masalah opini, kurasa.
6

Anda dapat menggunakan pembanding fungsi tanpa membungkusnya seperti ini:

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

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

yang menjengkelkan untuk mengetik setiap kali Anda memerlukan satu set jenis itu, dan dapat menyebabkan masalah jika Anda tidak membuat semua set dengan pembanding yang sama.

Tom Whittock
sumber
3

std::less<> saat menggunakan kelas khusus dengan operator<

Jika Anda berurusan dengan sekumpulan kelas kustom Anda yang telah operator<ditentukan, maka Anda bisa menggunakan std::less<>.

Seperti disebutkan di http://en.cppreference.com/w/cpp/container/set/find C ++ 14 telah menambahkan dua findAPI baru :

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

yang memungkinkan Anda melakukan:

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

Kompilasi dan jalankan:

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

Info lebih lanjut tentang std::less<>dapat ditemukan di: Apa itu pembanding transparan?

Diuji pada Ubuntu 16.10, g++6.2.0.

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