Usando std personalizado :: set comparator

106

Estoy tratando de cambiar el orden predeterminado de los elementos en un conjunto de enteros para que sean lexicográficos en lugar de numéricos, y no puedo compilar lo siguiente 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);
    ...
}

Obtuve el siguiente error:

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é estoy haciendo mal?

Omry Yadan
fuente

Respuestas:

159

Estás usando una función en la que deberías usar un functor (una clase que sobrecarga el operador () para que se pueda llamar 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 de functor, también puede usar un puntero de función (asumiendo que lex_comparees una función).

set<int64_t, bool(*)(const int64_t& lhs, const int64_t& rhs)> s(&lex_compare);
Yacoby
fuente
4
@Omry: Me interesaría saber qué compilador estás usando: codepad.org/IprafuVf
1
@Omry ¿Qué compilador estás usando?
4
@Omry El estándar C ++ dice que el segundo parámetro de plantilla debe ser el nombre de un tipo; el nombre de una función no es el nombre de un tipo.
6
¿Podemos usar decltype (lex_compare) para denotar el tipo de función?
Lewis Chan
2
@LewisChan el término correcto seríastd::set<int64_t, decltype(&lex_compare)> s(&lex_compare)
Nishant Singh
111

1. Solución C ++ 20 moderna

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 el elemento pasado como primer argumento se considera que va antes que el segundo en el orden débil estricto específico que define.

Demostración online

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 establecer el constructor

Demostración online

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

Luego úsalo, ya sea de esta manera:

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

Demostración online

o de esta manera:

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

Demostración online

4. Solución anterior que usa estructura con ()operador

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

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

Demostración online

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

Toma la función booleana

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

Y haz una estructura a partir de él usando std::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 online

diralik
fuente
3
En el ejemplo 1, ¿es necesario pasar cmp al constructor? ¿El conjunto construirá uno por sí mismo ya que el tipo lambda se proporciona como tipo de plantilla?
PeteUK
2
@PeteUK antes de que el comparador de C ++ 20 se pase al constructor. En C ++ 20 se puede utilizar un constructor sin argumentos. Gracias por la pregunta; la respuesta fue actualizada
diralik
1
@diralik Muchas gracias por la respuesta y la actualización de su ya excelente respuesta.
PeteUK
1
lambda genérico también parece funcionar para 1 y 2
ZFY
2
Eso 5. es una locura. Uno encuentra nuevos rincones y recovecos del idioma todos los días.
Jan Hošek
18

La respuesta de Yacoby me inspira a escribir un adaptador para encapsular el functor boilerplate.

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
fuente
17
Una cuestión de opinión, supongo.
6

Puede 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 necesita un conjunto de ese tipo y puede causar problemas si no crea todos los conjuntos con el mismo comparador.

Tom Whittock
fuente
3

std::less<> al usar clases personalizadas con operator<

Si está tratando con un conjunto de su clase personalizada que se ha operator<definido, entonces puede usar std::less<>.

Como se menciona en http://en.cppreference.com/w/cpp/container/set/find C ++ 14 ha agregado dos nuevas findAPI:

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

que te permiten hacer:

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

Compilar y ejecutar:

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

std::less<>Puede encontrar más información sobre : ¿Qué son los comparadores transparentes?

Probado en Ubuntu 16.10, g++6.2.0.

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