Usando el comparador std::set personalizado
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 for ‘template<class _Key, class _Compare, class _Alloc> class std::set’
error: expected a type, got ‘lex_compare’
¿Qué estoy haciendo mal?
1. Solución moderna de C++20
auto cmp = [](int a, int b) { return ... };
std::set<int, decltype(cmp)> s;
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);
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;
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
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_compare
sea una función).
set<int64_t, bool(*)(const int64_t& lhs, const int64_t& rhs)> s(&lex_compare);
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!
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.