Usando el comparador std::set personalizado

Resuelto Omry Yadan asked hace 14 años • 7 respuestas

Estoy intentando cambiar el orden predeterminado de los elementos en un conjunto de números enteros para que sean lexicográficos en lugar de numéricos, y no puedo compilar lo siguiente con g++:

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

Obtuve el siguiente error:

error: type/value mismatch at argument 2 in template parameter list fortemplate<class _Key, class _Compare, class _Alloc> class std::set’
error:   expected a type, got ‘lex_compare’

¿Qué estoy haciendo mal?

Omry Yadan avatar Apr 12 '10 16:04 Omry Yadan
Aceptado

1. Solución moderna de C++20

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

Usamos la función lambda como comparador. Como de costumbre, el comparador debe devolver un valor booleano, indicando si se considera que el elemento pasado como primer argumento va antes que el segundo en el orden débil estricto específico que define.

Demostración en línea

2. Solución moderna de C++11

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

Antes de C++ 20 necesitamos pasar lambda como argumento para configurar el constructor

Demostración en línea

3. Similar a la primera solución, pero con función en lugar de lambda

Hacer comparador como función booleana habitual

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

Entonces úsalo, ya sea de esta manera:

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

Demostración en línea

o de esta manera:

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

Demostración en línea

4. Solución antigua usando estructura con ()operador

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

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

Demostración en línea

5. Solución alternativa: crear una estructura a partir de una función booleana

Tomar la función booleana

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

Y haz una estructura a partir de ella usandostd::integral_constant

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

Finalmente, use la estructura como comparador.

std::set<X, Cmp> set;

Demostración en línea

diralik avatar Sep 09 '2017 07:09 diralik

Estás usando una función donde deberías usar un functor (una clase que sobrecarga el operador () para que pueda llamarse como una función).

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

Luego usa el nombre de la clase como parámetro de tipo.

set<int64_t, lex_compare> s;

Si desea evitar el código repetitivo del funtor, también puede utilizar un puntero de función (suponiendo que lex_comparesea una función).

set<int64_t, bool(*)(const int64_t& lhs, const int64_t& rhs)> s(&lex_compare);
Yacoby avatar Apr 12 '2010 09:04 Yacoby

La respuesta de Yacoby me inspira a escribir un adaptador para encapsular el texto estándar del funtor.

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!

¡Vaya, creo que valió la pena!

Potatoswatter avatar Apr 12 '2010 11:04 Potatoswatter

Puedes usar un comparador de funciones sin envolverlo así:

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

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

lo cual es irritante escribir cada vez que necesitas un conjunto de ese tipo y puede causar problemas si no creas todos los conjuntos con el mismo comparador.

Tom Whittock avatar Apr 03 '2017 14:04 Tom Whittock