Рассмотрим следующий пример кода:

// Example program
#include <iostream>
#include <string>
#include <algorithm>

class A
{
public:
    A( const std::string& name ) : name( name ) {}

    inline const std::string& getName() const { return name; }

private:
    std::string name;
};

int main()
{
    std::vector<A> v;
    v.push_back( A("b") );
    v.push_back( A("a") );

    // want to sort the container, based on it's name!
}

Я знаю, как это сделать (определить внутренний или внешний operator<, либо объявить структуру / класс функтора, предоставляющего operator() и передать его в функцию std::sort), например:

bool operator<( const A& a ) const
{
    return getName() < a.getName();
}

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

Можно ли запросить сортировку на основе значения результата функции-члена класса (если у этого есть доступный компаратор, очевидно)?

Что-то типа:

std::sort( v.begin(), v.end(), &(A::getName) );

Без объявления нового оператора или функтора?

Необязательный вопрос: что, если вектор содержит указатели (std::vector<A*> v) ... может ли однострочный оператор отсортировать его на основе результата A::getName?

1
jpo38 29 Апр 2016 в 10:37

2 ответа

Лучший ответ

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

std::sort(v.begin(), v.end(), [] (A const& a, A const& b) {
        return a.getName() < b.getName();
    });

Он длиннее, чем ваша версия, но вам не нужно ничего писать до вызова sort.

Если вам нужно часто делать такие вещи, вы можете поместить все в небольшой функтор (и позволить ему работать как для A, A*, так и для интеллектуальных указателей), он использует mem_fn (спасибо @TC):

template <typename T, typename R>
struct cmp_attr {

    using fun_t = R (T::*) () const;

    cmp_attr (fun_t g) : _g(g) { }

    template <typename U>
    bool operator() (U const& a, U const& b) const {
        auto fn = std::mem_fn(_g);
        return fn(a) < fn(b);
    }

private:
    fun_t _g;
};

// Utility function to have template parameters deduced, a like std::make_pair
template<typename T, typename R>
auto make_cmp_attr (R (T::*g) () const) {
    return cmp_attr<T, R>(g);
}

Затем:

struct A { const std::string& getName(); }
struct B: public A { }

std::vector<A> v1; // vector of A
std::sort(v1.begin(), v1.end(), make_cmp_attr(&A::getName));
std::vector<A*> v2; // vector of A*
std::sort(v2.begin(), v2.end(), make_cmp_attr(&A::getName));
std::vector<B> v3; // vector of child class
std::sort(v3.begin(), v3.end(), make_cmp_attr(&A::getName));
std::vector<B*> v4; // vector of pointer of child class
std::sort(v4.begin(), v4.end(), make_cmp_attr(&A::getName));
7
Holt 29 Апр 2016 в 09:20

Я бы рекомендовал не перегружать operator< только потому, что вы имеете в виду один конкретный вариант использования. Сохраняйте логику сортировки там, где она вам нужна; это не часть типа.

Если вы хотите регулярно сортировать по проекциям, возможно, стоит написать небольшой помощник:

template <typename C, typename R>
struct SortByPred
{
    using P = R (C::*)() const;

    P const p_;
    SortByPred(P p) : p_(p) {}

    template <typename T>
    bool operator()(T const & lhs, T const & rhs) const
    {
        return (lhs.*p_)() < (rhs.*p_)();
    }
};

template <typename C, typename R>
SortByPred<C, R> SortBy(R (C::*p)() const)
{
    return SortByPred<C, R>(p);
}

Применение:

std::sort(v.begin(), v.end(), SortBy(&A::getName));
1
Kerrek SB 29 Апр 2016 в 08:00