C++: Предоставление шаблонной функции сравнения для std::sort

Предположим, я хочу получить std::sort для сортировки вектора указателей на int на основе значения int, на которое указывают указатели. Игнорируйте очевидную проблему с производительностью. Просто да? Сделайте функцию:

bool sort_helper(const int *a, const int *b) 
{   
    return *a < *b;
}   

и передать в std::sort.

Теперь, если мы также хотим сделать то же самое с вектором указателей на большие объекты. Применяется то же самое: сначала мы определяем оператор < в объекте, затем создаем функцию следующего содержания:

bool sort_helper(const ob_type *a, const ob_type *b) 
{   
    return *a < *b;
}   

или что-то еще, поставьте это в std:: sort.

Теперь, и здесь все становится сложнее: что, если мы хотим отсортировать вектор указателей на любой тип с любой произвольной функцией сравнения (мы делаем предположение, что какой бы тип мы ни использовали эту функцию, сможет работать с ним ) — предоставить шаблонную версию функции sort_helper выше очень просто:

template <class ob_type>
bool sort_helper(const ob_type *a, const ob_type *b) 
{   
    return *a < *b;
}   

Однако предоставить произвольную функцию сравнения сложнее: что-то вроде этого:

template <typename comparison_function, class ob_type>
bool sort_template_helper(const ob_type *a, const ob_type *b)
{
    return comparison_function(*a, *b);
}

template <typename comparison_function, class iterator_type>
void t_sort(const iterator_type &begin, const iterator_type &end, comparison_function compare)
{
    std::sort(begin, end, sort_template_helper<compare>);
}

это то, что я хотел бы сделать, но делаю это:

bool less_than(const int a, const int b) 
{   
    return a < b;
}   

void do_stuff()
{
    t_sort(ipoint_vector.begin(), ipoint_vector.end(), sort_template_helper<less_than>);
}

Не работает. Как я могу отсортировать вектор указателей на известный тип по значению объектов, на которые он указывает, используя произвольную функцию сравнения, предоставленную std::sort? Предположим, что тестовый пример, который я здесь представляю, является безумно упрощенной версией фактического сценария, и что есть веские причины для того, чтобы делать что-то таким образом, что заняло бы слишком много времени, чтобы углубиться и отвлечься от проблемы.

[РЕДАКТИРОВАТЬ: по разным причинам я ищу решение, которое также работает на С++ 03 - спасибо Ниру за его ответ на С++ 14]


person metamorphosis    schedule 10.05.2017    source источник


Ответы (3)


Вы не можете передать указатель шаблона функции в С++, что вы можете сделать, так это создать функтор с шаблоном operator() (что-то очень похожее на лямбда с автоматическими параметрами):

#include <algorithm>
#include <vector>
#include <cassert>

struct less {
    template <class T>
    bool operator()(T first, T second) const {
        return first < second;
    }
};

template <class Cmp>
struct cmp_ptr {
    Cmp cmp;

    cmp_ptr(Cmp cmp):cmp(cmp) { }
    cmp_ptr() { }

    template <class T>
    bool operator()(T first, T second) const {
        return cmp(*first, *second);
    }
};

template <class Iter, class Cmp>
bool is_sorted(Iter beg, Iter end, Cmp cmp) {
   Iter prev = beg;
   Iter next = beg;
   for (next++; next != end; prev++, next++) {
      if (cmp(*next, *prev)) {
         return false;
      }
   }
   return true;
}

int main() {
    std::vector<int*> v;
    v.push_back(new int(10));
    v.push_back(new int(1));
    v.push_back(new int(5));
    v.push_back(new int(7));
    v.push_back(new int(3));
    v.push_back(new int(2));
    std::sort(v.begin(), v.end(), cmp_ptr<less>());
    assert(::is_sorted(v.begin(), v.end(), cmp_ptr<less>()));
}

[демонстрация]

Помните, что оператор должен иметь квалификатор const, чтобы вызывающая сторона могла получить к нему доступ из временного объекта.

person W.F.    schedule 10.05.2017
comment
На самом деле не отвечает на вопрос, как вызвать сортировку для произвольного типа, используя произвольную функцию сравнения для сортировки указателей по элементам, на которые они указывают - ваше решение предполагает шаблонную функцию сравнения, которая специально предназначена для этого сценария, не позволяет для использования произвольной функции сравнения, предназначенной для работы с элементом, а не с указателем элемента. - person metamorphosis; 10.05.2017
comment
@metamorphosis Я имею в виду - зачем явно указывать тип элемента коллекции, когда его можно легко вывести ... - person W.F.; 10.05.2017
comment
Спасибо, я думаю, что это полностью отвечает на мой вопрос - Ура. - person metamorphosis; 10.05.2017
comment
@metamorphosis Этот ответ неверен; это не работает для функторов с сохранением состояния, которые вполне законны для использования в качестве компараторов. В отличие, скажем, от другого ответа кашель, который делает это правильно. - person Nir Friedman; 10.05.2017
comment
@NirFriedman, вы также не использовали взаимную лямбду ... ;) - person W.F.; 10.05.2017
comment
@В.Ф. Извините, что вы подразумеваете под взаимной лямбдой? Шаблон оператора вызова, вероятно, приятный штрих, он освобождает вас от необходимости явно указывать тип. Я начал свое решение с 11 лямбд, которые не могут выразить это, а затем просто переписал в 03, поэтому я пропустил это. - person Nir Friedman; 10.05.2017
comment
@В.Ф. Я не уверен, что понимаю, моя лямбда не помечена как изменчивая, ваш объект функции () помечен как константа, похоже, то же самое? Объекты/лямбда-функции с состоянием не должны изменять свое состояние, чтобы быть полезными, если вы это имеете в виду. Однако я краду шаблонный оператор() ;-). - person Nir Friedman; 10.05.2017
comment
@NirFriedman, я имел в виду, что мои функторы не позволяют изменять состояния, но и ваши: D Хороший вывод, однако, что это можно смягчить, внедрив неконстантный operator(). - person W.F.; 10.05.2017
comment
@В.Ф. Ни один из них не позволяет изменять состояние, но ваш вообще не допускает никакого состояния (что не одно и то же), потому что вы по умолчанию создаете компаратор в своем функциональном объекте вместо того, чтобы передавать его, что является правильным. - person Nir Friedman; 10.05.2017

В основном вам нужна функция более высокого порядка: функция, которая возвращает функцию.

template <class T, class F>
auto make_pointee_comparison(F f) {
    return [=] (T const * l, T const * r) { return f(*l, *r); };
}

Здесь я должен явно указать T; вы могли бы с дополнительным программированием вывести T, но может быть довольно сложно заставить его работать правильно как для объектов функций, так и для указателей на функции.

Изменить: чтобы это работало в С++ 03, мы должны, очевидно, отказаться от использования лямбда-выражений. Однако преобразовать лямбду в объект функции довольно просто. Объявляем структуру:

template <class F>
struct PointeeComparisonHelper {
    PointeeComparisonHelper(F f) : m_f(f) {}

    template <class T>
    bool operator()(T const * l, T const * r) const {
        return m_f(*l, *r);
    }

    F m_f;
};

template <class F>
PointeeComparisonHelper<F> make_pointee_comparison(F f) {
    return PointeeComparisonHelper<F>(f);
}

Изменить: я создал шаблон оператора вызова в примере 03; чтобы сделать это с лямбдой, вам нужен С++ 14, а не только 11. Если вы используете форму 03, вам не нужно явно указывать <int> до make_pointee_comparison.

Использование:

auto c = make_pointee_comparison<int>([] (int x, int y) { return x < y; });
int x = 5;
int y = 6;
std::cerr << c(&x, &y) << c(&y, &x);

Который печатает 10 (true false). Обратите внимание, что здесь используется объект функции, а не указатель на функцию, что более характерно для C++. Но вы также можете передать указатель на функцию:

bool compare(int x, int y) { return x > y; }

auto c2 = make_pointee_comparison<int>(&compare);
std::cerr << c2(&x, &y) << c2(&y, &x);

Затем вы можете написать свою функцию следующим образом:

template <typename comparison_function, class iterator_type>
void t_sort(const iterator_type &begin, const iterator_type &end, comparison_function compare)
{
    using deref_type = const decltype(*begin);
    std::sort(begin, end, make_pointee_comparison<deref_type>(compare));
}
person Nir Friedman    schedule 10.05.2017
comment
Спасибо! Протестировали, и, о чудо, работает! есть ли эквивалент для С++ 03? - person metamorphosis; 10.05.2017
comment
@metamorphosis ах дерьмо :-). Вероятно, нам следует начать поощрять людей использовать тег 03, я думаю, что люди как бы предполагают, что 11 — это значение по умолчанию. Хорошо, ничего, если я дам набросок решения 03 почти без подробностей? - person Nir Friedman; 10.05.2017
comment
Абсолютно. Для тех, кто читает, первое решение выше - только C++ 14 и выше (в противном случае выведенный тип возврата не будет работать). - person metamorphosis; 10.05.2017
comment
Не волнуйтесь, если вам нечего добавить, я думаю, что у W.T есть решение на стороне 03. Но спасибо. Проголосовал за вас обоих. - person metamorphosis; 10.05.2017

думаю, вы ищете что-то вроде этого:

template <typename comparison_function, class iterator_type>
void sort_deref(const iterator_type &begin, const iterator_type &end, comparison_function compare) {
    std::sort(begin, end, [compare](auto *a, auto *b) { compare(*a,*b); });
}

// example usage:
sort_deref(std::begin(container), std::end(container), std::less<>());

Вы пытаетесь передать значение типа comparison_function шаблону, ожидающему тип.

person Andrei R.    schedule 10.05.2017