Usando o comparador std :: set personalizado

106

Estou tentando alterar a ordem padrão dos itens em um conjunto de inteiros para serem lexicográficos em vez de numéricos e não consigo compilar o seguinte com 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);
    ...
}

Estou tendo o erro a seguir:

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

O que estou fazendo de errado?

Omry Yadan
fonte

Respostas:

159

Você está usando uma função em que deve usar um functor (uma classe que sobrecarrega o operador () para que possa ser chamado como uma função).

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

Você então usa o nome da classe como o parâmetro de tipo

set<int64_t, lex_compare> s;

Se você quiser evitar o código clichê do functor, você também pode usar um ponteiro de função (assumindo que lex_compareseja uma função).

set<int64_t, bool(*)(const int64_t& lhs, const int64_t& rhs)> s(&lex_compare);
Yacoby
fonte
4
@Omry: Eu estaria interessado em saber qual compilador você está usando: codepad.org/IprafuVf
1
@Omry Qual compilador você está usando?
4
@Omry O padrão C ++ diz que o segundo parâmetro do modelo deve ser o nome de um tipo - um nome de função não é o nome de um tipo.
6
podemos usar decltype (lex_compare) para denotar o tipo de função?
Lewis Chan
2
O termo correto de @LewisChan seriastd::set<int64_t, decltype(&lex_compare)> s(&lex_compare)
Nishant Singh
109

1. Solução C ++ 20 moderna

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

Usamos a função lambda como comparador. Como de costume, o comparador deve retornar um valor booleano, indicando se o elemento passado como primeiro argumento é considerado anterior ao segundo na ordem estrita e fraca específica que define.

Demo online

2. Solução C ++ 11 moderna

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

Antes do C ++ 20, precisamos passar lambda como argumento para definir o construtor

Demo online

3. Semelhante à primeira solução, mas com função em vez de lambda

Faça comparador como função booleana usual

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

Em seguida, use-o desta forma:

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

Demo online

ou assim:

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

Demo online

4. Solução antiga usando struct com ()operador

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

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

Demo online

5. Solução alternativa: crie uma estrutura a partir da função booleana

Pegue a função booleana

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

E faça uma estrutura a partir dele usando std::integral_constant

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

Finalmente, use a estrutura como comparador

std::set<X, Cmp> set;

Demo online

diralik
fonte
3
No exemplo 1, cmp precisa ser passado para o construtor? O conjunto construirá um por si mesmo, pois o tipo lambda é fornecido como um tipo de modelo?
PeteUK
2
@PeteUK antes do comparador C ++ 20 ter que ser passado para o construtor. Em C ++ 20, o construtor sem argumentos pode ser usado. Obrigado pela pergunta; resposta foi atualizada
diralik
1
@diralik Muito obrigado pela resposta e atualização para sua já ótima resposta.
PeteUK
1
lambda genérico parece funcionar também para 1 e 2
ZFY
2
Esse 5. é insano. A cada dia encontramos novos cantos e recantos da língua.
Jan Hošek
18

A resposta de Yacoby me inspira a escrever um adaptador para encapsular o boilerplate do 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!

Nossa, acho que valeu a pena!

Potatoswatter
fonte
17
Uma questão de opinião, eu acho.
6

Você pode usar um comparador de função sem agrupá-lo da seguinte maneira:

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

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

o que é irritante de digitar toda vez que você precisa de um conjunto desse tipo e pode causar problemas se você não criar todos os conjuntos com o mesmo comparador.

Tom Whittock
fonte
3

std::less<> ao usar classes personalizadas com operator<

Se você está lidando com um conjunto de sua classe personalizada que foi operator<definida, então você pode apenas usar std::less<>.

Conforme mencionado em http://en.cppreference.com/w/cpp/container/set/find, C ++ 14 adicionou duas novas findAPIs:

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

que permitem que você faça:

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

Compile e execute:

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

Mais informações sobre std::less<>podem ser encontradas em: O que são comparadores transparentes?

Testado no Ubuntu 16.10, g++6.2.0.

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