Использование настраиваемого компаратора std :: set

110

Я пытаюсь изменить порядок элементов по умолчанию в наборе целых чисел, чтобы он был лексикографическим, а не числовым, и я не могу получить следующее для компиляции с 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);
    ...
}

Я получаю следующую ошибку:

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

Что я делаю не так?

Омри Ядан
источник

Ответы:

160

Вы используете функцию, в которой вы должны использовать функтор (класс, который перегружает оператор (), чтобы его можно было вызывать как функцию).

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

Затем вы используете имя класса в качестве параметра типа

set<int64_t, lex_compare> s;

Если вы хотите избежать использования шаблонного кода функтора, вы также можете использовать указатель на функцию (при условии, lex_compareчто это функция).

set<int64_t, bool(*)(const int64_t& lhs, const int64_t& rhs)> s(&lex_compare);
Якоби
источник
4
@Omry: Мне было бы интересно узнать, какой компилятор вы используете: codepad.org/IprafuVf
1
@Omry Какой компилятор вы используете?
4
@Omry Стандарт C ++ говорит, что второй параметр шаблона должен быть именем типа - имя функции не является именем типа.
6
можем ли мы использовать decltype (lex_compare) для обозначения типа функции?
Льюис Чан
2
@LewisChan правильным термином будетstd::set<int64_t, decltype(&lex_compare)> s(&lex_compare)
Нишант Сингх
114

1. Современное решение C ++ 20

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

Мы используем лямбда-функцию в качестве компаратора. Как обычно, компаратор должен возвращать логическое значение, указывающее, считается ли элемент, переданный в качестве первого аргумента, предшествующим второму в конкретном строгом слабом порядке, который он определяет.

Онлайн-демонстрация

2. Современное решение C ++ 11

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

До C ++ 20 нам нужно передать лямбда в качестве аргумента для конструктора установки

Онлайн-демонстрация

3. Аналогично первому решению, но с функцией вместо лямбды.

Сделайте компаратор как обычную логическую функцию

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

Затем используйте его так:

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

Онлайн-демонстрация

или так:

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

Онлайн-демонстрация

4. Старое решение с использованием структуры с ()оператором

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

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

Онлайн-демонстрация

5. Альтернативное решение: создать структуру из логической функции

Возьмите логическую функцию

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

И сделайте из него структуру, используя std::integral_constant

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

Наконец, используйте структуру как компаратор

std::set<X, Cmp> set;

Онлайн-демонстрация

Диралик
источник
3
В примере 1 нужно ли передавать cmp в конструктор? Будет ли набор сам создавать, поскольку лямбда-тип задается как тип шаблона?
PeteUK
2
@PeteUK до того, как компаратор C ++ 20 должен быть передан в конструктор. В C ++ 20 можно использовать конструктор без аргументов. Спасибо за вопрос; ответ обновлен
diralik
1
@diralik Большое спасибо за ответ и обновление до уже отличного ответа.
PeteUK,
2
Это 5. безумие. Каждый день находишь новые уголки и закоулки языка.
Ян Гошек,
18

Ответ Якоби вдохновляет меня написать адаптер для инкапсуляции шаблона функтора.

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!

Вау, я думаю, это того стоило!

Potatoswatter
источник
17
Думаю, дело в мнении.
6

Вы можете использовать компаратор функций, не оборачивая его так:

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

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

что раздражает печатать каждый раз, когда вам нужен набор этого типа, и может вызвать проблемы, если вы не создадите все наборы с одним и тем же компаратором.

Том Уитток
источник
3

std::less<> при использовании пользовательских классов с operator<

Если вы имеете дело с набором определенного вами пользовательского класса operator<, вы можете просто использовать std::less<>.

Как упоминалось на http://en.cppreference.com/w/cpp/container/set/find, C ++ 14 добавил два новых findAPI:

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

которые позволяют вам делать:

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

Скомпилируйте и запустите:

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

Более подробную информацию std::less<>можно найти по адресу: Что такое прозрачные компараторы?

Проверено на Ubuntu 16.10, g++6.2.0.

Чиро Сантилли 郝海东 冠状 病 六四 事件 法轮功
источник