More ergonomic / convenient STL algorithm calls -- sort order & member field extraction using std::invoke
Indeed, your sortby when used in the style shown, with data members, will not be quite as fast as if you used the STL with a lambda. Look at the difference in assembly between

struct Date {
    int year, month, day;
};

void test1(std::array&lt;Date, 100&gt;&amp; a) {
    sortby(a, &amp;Date::month);
    sortby(a, &amp;Date::year);
}

void test2(std::array&lt;Date, 100&gt;&amp; a) {
    sortby(a, [](const auto&amp; x){ return x.month; });
    sortby(a, [](const auto&amp; x){ return x.year; });
}


In test1, the difference between &amp;Date::month and &amp;Date::year is encoded in the runtime value of the member pointer. In test2, the difference is encoded in the compile-time type of the lambda. So test2 instantiates two different template instantiations, whereas test1 calls the same instantiation twice with different inputs. (But if both calls are inlined, the optimizer might be able to hide the difference.)

However, this is not the fault of your sortby helper; it's the fault of using member pointers instead of custom lambdas. You can observe the same differences happening if you call std::sort with a function pointer, versus calling std::sort with an instance of some bespoke type such as std::less&lt;&gt; or std::greater&lt;&gt;.



You pass Member&amp;&amp; m by forwarding reference, but then you use it as an lvalue (without forwarding it again). I think I've mentioned before: if you don't intend to forward an argument, then you shouldn't be using a forwarding reference with it. In this case, you just need to look at the value of the member-pointer m without modifying it, so const Member&amp; m would be appropriate. Or, since member data pointers and custom lambdas are both going to be small trivial types, and member function pointers are also trivial types (if slightly larger), we should seriously think about taking Member m directly by value.

Notice that you already take Comparison comp by value.

The comment about using std::begin(c) over c.begin() is accurate. If you want your sortby template to work for C-style arrays, then you should use std::begin(c). Remember, you can't pass arrays by value (they decay to pointers) — but you can certainly pass arrays by reference, and that's what you're doing here when you sortby an array "in place."

The vast majority of lambdas you write should just take [&amp;]. I don't see any benefit to writing [&amp;m, &amp;comp] when it ends up meaning the same thing as [&amp;].

So I'd write your helper like this:

template&lt;class Container, class Member, class Comparison = std::less&lt;&gt;&gt;
void sortby(Container&amp; c, Member m, Comparison comp = Comparison()) {
    using std::begin; using std::end;
    std::sort(begin(c), end(c), [&amp;](const auto&amp; a, const auto&amp; b) {
        return comp(std::invoke(m, a), std::invoke(m, b));
    });
}


using std::begin; is the "std::swap two-step," familiar to anyone who's used std::swap before. It means "consider using std::begin for unqualified calls, but if someone has provided a better-matching ADL version of begin, then we'll happily use that version instead." This is certainly overkill — I mean we could just write std::begin(c), std::end(c), — but the two-step is how all the standard customization points are meant to be used as far as I know, so we might as well stick to it.

Vice versa, I would personally shy away from using the two-step in main —

using os::algo::sortby;


This seems more confusing than helpful. Are you expecting that someone else might provide a better-matching ADL sortby? If not, then sortby(x,y,z) will always mean os::algo::sortby(x,y,z)... and if it is os::algo::sortby, why not say so?