Select member for comparison in sort or search This code is predicated on the notion that when sorting (or using a sorted collection of items, such as with std::lower_bound) you're usually comparing a single field in each object being sorted/searched.

So a typical sort might look something like:

// Sort by age
std::sort(people.begin(), people.end(), 
    [](Person const &amp;a, Person const &amp;b) { return a.age &lt; b.age; });


Or:

// sort by height, descending:
std::sort(people.begin(), people.end(), 
    [](Person const &amp;a, Person const &amp;b) { return b.height &lt; a.height; });


Although the lambda is cleaner than C++98 code, I'd really rather be able to specify something like by(height, descending). I haven't (yet) figured out a way to (quite) get to that ideal, but I think this gets us quite a bit closer:

#ifndef SORT_MEMBERS_H_
#define SORT_MEMBERS_H_

namespace sort {
    namespace detail {
        template &lt;class FieldType, class RecordType&gt;
        struct Desc {
            FieldType RecordType::*fn;
        public:
            Desc(FieldType RecordType::*fn) : fn(fn) {}

            bool operator()(RecordType const &amp;a, RecordType const &amp;b) {
                return b.*fn &lt; a.*fn;
            }
        };

        template &lt;class FieldType, class RecordType&gt;
        struct Asc {
            FieldType RecordType::*fn;
        public:
            Asc(FieldType RecordType::*fn) : fn(fn) {}

            bool operator()(RecordType const &amp;a, RecordType const &amp;b) {
                return a.*fn &lt; b.*fn;
            }
        };
    }

    template &lt;class FieldType, class RecordType&gt;
    detail::Asc&lt;FieldType, RecordType&gt; ascending(FieldType RecordType::*b) {
        return detail::Asc&lt;FieldType, RecordType&gt;(b);
    }

    template &lt;class FieldType, class RecordType&gt;
    detail::Desc&lt;FieldType, RecordType&gt; descending(FieldType RecordType::*b) {
        return detail::Desc&lt;FieldType, RecordType&gt;(b);
    }
}

#endif


Example usage would be something like this:

#include &lt;vector&gt;
#include &lt;algorithm&gt;
#include &lt;iostream&gt;
#include &lt;string&gt;
#include "sort_members.h"

struct Person
{
    std::string first_name;
    std::string last_name;
    int age;

    friend std::ostream &amp;operator&lt;&lt;(std::ostream &amp;os, Person const &amp;person) {
        return os &lt;&lt; person.first_name &lt;&lt; " " &lt;&lt; person.last_name &lt;&lt; ", " &lt;&lt; person.age;
    }
};

int main() {
    std::vector&lt;Person&gt; people = { 
        { "John", "Rambo", 42 }, 
        { "Joffrey", "Baratheon", 14 }, 
        { "Kyle", "Reese", 25 } 
    };

    std::sort(people.begin(), people.end(), sort::descending(&amp;Person::age));

    std::cout &lt;&lt; "Sorted by age, descending:\n";
    for (const auto&amp; person : people)
        std::cout &lt;&lt; person &lt;&lt; "\n";

    std::cout &lt;&lt; "\nSorted by last name:\n";
    std::sort(people.begin(), people.end(), sort::ascending(&amp;Person::last_name));
    for (const auto&amp; person : people)
        std::cout &lt;&lt; person &lt;&lt; "\n";
}


I'd welcome any comments--but I'm mostly interested in the part in the header; the file using it is just a demo, not anything very interesting in itself, beyond the fact that it demonstrates how the preceding code is used.
This might be a bit nitpicky, but Asc and Desc aren't that good of names for classes. Granted, they are in your detail namespace, so it should be fine, but why not just write out the names Ascending and Descending?

I do find Asc more ambiguous than Desc, because I see "desc" as shorthand for "descending" more often.



Both Desc and Asc are structs, which means that your public: is redundant. You probably want to make them classes, as it seems you want to hide the fn member variable.



Why restrict the sort-by function to being by member variables? You could easily let it work for any callable object:

template &lt;class F&gt;
class Asc {
    F fn;
public:
    Asc(F fn) : fn(std::move(fn)) {}

    template &lt;typename Lhs, typename Rhs&gt;
    bool operator()(Lhs&amp;&amp; a, Rhs&amp;&amp; b) {
        // by separating the types like this, we gain a little bit
        // of generality. Not that it matters if we are using it
        // with std::sort only. However, really what the overall
        // utility is doing is defining a comparator, so you might
        // want to keep it more general
        return fn(std::forward&lt;Lhs&gt;(a)) &lt; fn(std::forward&lt;Rhs&gt;(b));
    }
};


I know that, at this point, this removes the ability to use member functions at all, but we can use std::invoke to remedy that:

    template &lt;typename Lhs, typename Rhs&gt;
    bool operator()(Lhs&amp;&amp; a, Rhs&amp;&amp; b) {
        return std::invoke(fn, std::forward&lt;Lhs&gt;(a)) &lt; std::invoke(fn, std::forward&lt;Rhs&gt;(b));
    }


Although std::invoke does require C++17. If you don't want to require C++17, this Stack Overflow question discusses how to get the behavior of std::invoke in C++11. In particular, you can use std::ref:

    template &lt;typename Lhs, typename Rhs&gt;
    bool operator()(Lhs&amp;&amp; a, Rhs&amp;&amp; b) {
        return std::ref(fn)(std::forward&lt;Lhs&gt;(a)) &lt; std::ref(fn)(std::forward&lt;Rhs&gt;(b));
    }


Although you may want to wrap the std::ref(callable)(args...) into a utility function to make the code more self-documenting



There's also a small problem, where if the field we are sorting by is a pointer, you can have undefined behavior. Comparing pointers with operator&lt; is only allowed if the pointers are from the same array. So it would be better to use std::less:

    template &lt;typename Lhs, typename Rhs&gt;
    bool operator()(Lhs&amp;&amp; a, Rhs&amp;&amp; b) {
        return std::less&lt;&gt;()(std::ref(fn)(std::forward&lt;Lhs&gt;(a)), std::ref(fn)(std::forward&lt;Rhs&gt;(b)));
    }


But if you don't have C++14, you'd have to write something like this instead:

    template &lt;typename T&gt;
    bool operator()(T const&amp; a, T const&amp; b) {
        auto cmp = std::less&lt;
            std::decay_t&lt;decltype(std::ref(fn)(a))&gt;
        &gt;();
        return cmp(std::ref(fn)(a), std::ref(fn)(b));
    }




Also, we can actually combine both Asc and Desc. Currently, the only difference is that you call operator&lt; with the arguments in the other order.

Instead, what we can do is take a comparator in our class:

template &lt;class F, class Compare = std::less&lt;&gt;&gt;
class SortBy {
    F fn;
    Compare cmp;
public:
    SortBy(F fn, Compare cmp = {})
        : fn(std::move(fn))
        , cmp(std::move(cmp))
    {}

    template &lt;typename Lhs, typename Rhs&gt;
    bool operator()(Lhs&amp;&amp; a, Rhs&amp;&amp; b) {
        return cmp(
            std::invoke(fn, std::forward&lt;Lhs&gt;(a)),
            std::invoke(fn, std::forward&lt;Rhs&gt;(b))
        );
    }
};


Then, you can reduce your ascending and descending functions down to a single function:

template&lt;typename F, typename Compare = std::less&lt;&gt;&gt;
SortBy&lt;F, Compare&gt; sort_by(F fn, Compare cmp = {}) {
    // by using uniform initialization, we don't have to repeat the
    // type of SortBy&lt;...&gt;
    return { std::move(fn), std::move(cmp) };
}


If the user wanted to sort descending, they'd have to use sort_by(..., std::greater&lt;&gt;). That does require that an operator&gt; is defined for their type, but they can always define their own comparator that defines a greater-than by using operator&lt;



Orthogonally, splitting the Lhs and Rhs arguments for the operator() makes our SortBy a transparent comparator, so we should indicate this by adding a is_transparent typedef:

template &lt;class F, class Compare = std::less&lt;&gt;&gt;
class SortBy {
    F fn;
    Compare cmp;
public:
    using is_transparent = void;

    SortBy(F fn, Compare cmp = {})
        : fn(std::move(fn))
        , cmp(std::move(cmp))
    {}

    template &lt;typename Lhs, typename Rhs&gt;
    bool operator()(Lhs&amp;&amp; a, Rhs&amp;&amp; b) {
        return cmp(
            std::invoke(fn, std::forward&lt;Lhs&gt;(a)),
            std::invoke(fn, std::forward&lt;Rhs&gt;(b))
        );
    }
};