Select member for comparison in sort or search
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))
        );
    }
};