Utilisation du comparateur std :: set personnalisé

109

J'essaie de changer l'ordre par défaut des éléments dans un ensemble d'entiers pour qu'ils soient lexicographiques au lieu de numériques, et je ne parviens pas à compiler les éléments suivants avec 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);
    ...
}

J'obtiens l'erreur suivante:

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

Qu'est-ce que je fais mal?

Omry Yadan
la source

Réponses:

160

Vous utilisez une fonction où, comme vous devriez utiliser un foncteur (une classe qui surcharge l'opérateur () afin qu'il puisse être appelé comme une fonction).

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

Vous utilisez ensuite le nom de la classe comme paramètre de type

set<int64_t, lex_compare> s;

Si vous voulez éviter le code standard du foncteur, vous pouvez également utiliser un pointeur de fonction (en supposant que lex_comparec'est une fonction).

set<int64_t, bool(*)(const int64_t& lhs, const int64_t& rhs)> s(&lex_compare);
Yacoby
la source
4
@Omry: Je serais intéressé de savoir quel compilateur vous utilisez: codepad.org/IprafuVf
1
@Omry Quel compilateur utilisez-vous?
4
@Omry Le standard C ++ dit que le deuxième paramètre de modèle doit être le nom d'un type - un nom de fonction n'est pas le nom d'un type.
6
pouvons-nous utiliser decltype (lex_compare) pour désigner le type de fonction?
Lewis Chan
2
Le terme correct @LewisChan seraitstd::set<int64_t, decltype(&lex_compare)> s(&lex_compare)
Nishant Singh
113

1. Solution C ++ 20 moderne

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

Nous utilisons la fonction lambda comme comparateur. Comme d'habitude, le comparateur doit retourner une valeur booléenne, indiquant si l'élément passé en tant que premier argument est considéré comme précédant le second dans l'ordre strict strict qu'il définit.

Démo en ligne

2. Solution C ++ 11 moderne

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

Avant C ++ 20, nous devons passer lambda comme argument pour définir le constructeur

Démo en ligne

3. Similaire à la première solution, mais avec fonction au lieu de lambda

Rendre le comparateur comme une fonction booléenne habituelle

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

Ensuite, utilisez-le, soit de cette façon:

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

Démo en ligne

ou de cette façon:

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

Démo en ligne

4. Ancienne solution utilisant struct avec ()opérateur

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

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

Démo en ligne

5. Solution alternative: créer une structure à partir d'une fonction booléenne

Prendre la fonction booléenne

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

Et en faire une structure en utilisant std::integral_constant

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

Enfin, utilisez la structure comme comparateur

std::set<X, Cmp> set;

Démo en ligne

diralik
la source
3
Dans l'exemple 1, est-ce que cmp doit être passé dans le constructeur? L'ensemble en construira-t-il un lui-même car le type lambda est donné comme type de modèle?
PeteUK
2
@PeteUK avant que le comparateur C ++ 20 ne soit passé dans le constructeur. En C ++ 20, un constructeur sans arguments peut être utilisé. Merci pour la question; La réponse a été mise à jour
diralik le
1
@diralik Merci beaucoup pour la réponse et la mise à jour de votre réponse déjà excellente.
PeteUK le
1
lambda générique semble également fonctionner pour 1 et 2
ZFY le
2
C'est insensé. On trouve chaque jour de nouveaux coins et recoins de la langue.
Jan Hošek
18

La réponse de Yacoby m'inspire pour écrire un adaptateur pour encapsuler le passe-partout du foncteur.

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, je pense que ça valait la peine!

Potatoswatter
la source
17
Une question d'opinion, je suppose.
6

Vous pouvez utiliser un comparateur de fonctions sans l'envelopper comme suit:

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

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

ce qui est irritant à taper à chaque fois que vous avez besoin d'un ensemble de ce type, et peut causer des problèmes si vous ne créez pas tous les ensembles avec le même comparateur.

Tom Whittock
la source
3

std::less<> lors de l'utilisation de classes personnalisées avec operator<

Si vous avez affaire à un ensemble de votre classe personnalisée qui a été operator<définie, vous pouvez simplement utiliser std::less<>.

Comme mentionné sur http://en.cppreference.com/w/cpp/container/set/find C ++ 14 a ajouté deux nouvelles findAPI:

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

qui vous permettent de faire:

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

Compilez et exécutez:

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

Vous std::less<>trouverez plus d'informations sur : Que sont les comparateurs transparents?

Testé sur Ubuntu 16.10, g++6.2.0.

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