Matt Munson
Matt Munson

Reputation: 3003

c++ std::sort() a vector of objects by any comparable member using only one compare function

Is it possible to call std::sort() on a std::vector of objects in such a way that we can specify which member will be used to compare the objects, but without having to implement a seperate compare function for each member. We can assume that each member that we want to sort by will have the < operator defined. If not, what is the best approach when we want to be able to sort a container of objects by many different criteria.

Upvotes: 1

Views: 153

Answers (2)

T.C.
T.C.

Reputation: 137310

Here's something that does a lexicographical comparison using arbitrarily many members of any class. Needs C++14 for variadic templates and compile-time integer sequences. You can implement compile-time integer sequences yourself if you have C++11.

#include <tuple>
#include <utility> // for make_index_sequence

template<class T, typename... types>
struct member_comparer {
    member_comparer(types T::*...  args) : ptrs(args...) { }

    bool operator()(const T& t1, const T& t2) const {
        return do_compare(t1, t2, std::make_index_sequence<sizeof...(types)>());
    }

    private:

    template<size_t... indices>
    bool do_compare(const T& t1, const T& t2, std::index_sequence<indices...> ) const {
        return std::tie(t1.*std::get<indices>(ptrs)...) <
               std::tie(t2.*std::get<indices>(ptrs)...);
    }

    std::tuple<types T::* ...> ptrs;
};

template<class T, typename... types>
auto make_member_comparer(types T::*...  args) {
    return member_comparer<T, types...>(args...); 
}

You use it like:

struct A {
    int x;
    double y;
    float z;
};

auto compare_x_only = make_member_comparer(&A::x);
auto compare_y_then_x = make_member_comparer(&A::y, &A::x);

Demo.

Upvotes: 2

Mark Ransom
Mark Ransom

Reputation: 308120

You can have a comparison object that has a flag indicating which member to sort on.

class Comparo
{
    int m_field;
public:
    Comparo(int field)  : m_field(field) { }
    bool operator()(const MyClass & Left, const MyClass & right)
    {
        switch (m_field)
        {
            case 0:
                return left.A < right.A;
            case 1:
                return left.B < right.B;
        }
    }
};

std::vector<MyClass> vec = FillMyVector();
std::sort(vec.begin(), vec.end(), Comparo(0));  // sorts on field A
std::sort(vec.begin(), vec.end(), Comparo(1));  // sorts on field B

Upvotes: 7

Related Questions