Question

I often find myself using std::sort, std::max_element, and the like with a lambda that simply invokes a member function

std::vector<MyType> vec;
// populate...
auto m = std::max_element(std::begin(vec), std::end(vec),
    [](const MyType& a, const MyType& b) { return a.val() < b.val()})

this feels like a waste of characters and a loss of clarity. I'm aware that I could write another function/callable and pass a function pointer/callable object to these algorithm functions, but I often need to do this sort-by just once in a program and it doesn't strike me as a good way of addressing the problem. What I want to do, ideally is say:

auto m = std::max_element(std::begin(vec), std::end(vec), &MyType::val);

and have the objects be sorted by their val()s. Is there some part of the stdlib I'm overlooking that could assist me with this? or another simple way of doing it? I'd like to make what this is sorting or searching by as obvious as possible.

I'm aware that just &MyType::val isn't enough, I am looking for something that could perhaps wrap it, or provide a similar functionality without obscurring the meaning.

Was it helpful?

Solution

You can use std::mem_fn (or std::tr1::mem_fn)

int main()
{
    std::vector<MyType> vec;

    auto m = std::max_element(std::begin(vec), std::end(vec), compare_by(std::mem_fn(&MyType::field)));
}

Of course, this assumes that you have a utility like compare_by in your toolbox (as you should :)):

template <typename F>
struct CompareBy {
    explicit CompareBy(F&& f) : f(std::forward<F>(f)) {}
    template <typename U, typename V> 
        bool  operator()(U const& u, V const& v) const {
            return f(u) < f(v);
        }

private:
    F f;
};

template <typename F>
CompareBy<F> compare_by(F&& f) { return CompareBy<F>(std::forward<F>(f)); }

See it Live On Coliru

OTHER TIPS

You can do it without introducing any new functions (templated or not).

Just use bind and std::less

auto m = std::max_element(vec.begin(), vec.end(), 
    bind(less<>(), bind(&MyType::val, _1), bind(&MyType::val, _2)));

A templated comparator could help you:

template <typename StructureType,
          typename MemberType,
          MemberType StructureType::*member>
bool comparator(const StructureType& the_first, const StructureType& the_second)
{
  return the_first.*member < the_second.*member;
}

http://ideone.com/K8ytav

A bit of type traits magic could certainly allows you to avoid writing the type.

How about once overloading operator< for your custom type? This can be naturally done inside the class (or directly next to it), and then no further argument is required beside the iterators.

Passing your val() function isn't possible, as you must pass a binary operator.

EDIT: Having read the other valuable alternatives (also sehe's nice response), I want to confirm what I already mentioned in the comment below: In my opinion, nothing beats the readibility, locality and also flexibility of a lambda expression (--on the risk of writing some passages twice).

@Ryan Haining: I suggest you to keep it as in your original post.

An improvement on sehes answer, so as to avoid needing to use std::mem_fn would be provide a pointer to member overload for the compare_by function.

example usage

std::sort(std::begin(vec), std::end(vec), compare_by(&MyType::field));
std::sort(std::begin(vec), std::end(vec), compare_by(&MyType::field, std::greater<>{}));

the code to implement

#include <functional> // std::less
#include <utility> // std::move
#include <type_traits> // std::is_invocable_r
// Forward declaration
template<typename R, typename T, typename F = std::less<R>>
auto compare_by(R T::*, F = F{});

// Implementation
namespace detail {
template<typename T, typename F>
struct compare_by_t;

template<typename R, typename T, typename F>
struct compare_by_t<R T::*, F> : private F
{
    compare_by_t(F&& f, R T::*m): F{std::move(f)}, _member{m} {}
    R T::* _member;
    bool operator()(T const& x, T const& y) const
    {
        return F::operator()(x .* _member, y .* _member);
    }
};
} // detail

template<typename R, typename T, typename F>
auto compare_by(R T::* member, F f)
{
    static_assert(std::is_invocable_r<bool, F, R, R>::value);
    return detail::compare_by_t<R T::*, F>{ std::move(f), member };
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top