Search code examples
c++templateslambda

Why I can use a template function as parameter to std::sort but not a template lambda (with member function ptr as template parameter)


Sorry for the long title, as I stated this works:

#include <algorithm>
#include <iostream>
#include <vector>

struct S { int a{}, b{}; };
std::ostream &operator<<(std::ostream &o, const S &s) { return o << '{' << s.a << ',' << s.b << '}'; }

template <auto ptr>
bool f(const S &a, const S &b)
{
    return a.*ptr < b.*ptr;
}

int main()
{
    std::vector<S> v{{1,9},{2,8},{3,7},{4,6},{6,4},{7,3},{8,2},{9,1}};

    std::sort(v.begin(), v.end(), f<&S::b>);
    //          Sort by S::b -----> ^^^^^

    for (const auto &s : v)
        std::cout << s << ' ';
    // output: {9,1} {8,2} {7,3} {6,4} {4,6} {3,7} {2,8} {1,9} 

    return 0;
}

But this don't:

    …

    auto l = []<auto ptr>(const S &a, const S &b)
    {
        return a.*ptr < b.*ptr;
    };

    std::sort(v.begin(), v.end(), l<&S::b>);
    //         Sort by S::b? -----> ^^^^^
    …

This is the error:

error: no match for 'operator<' (operand types are 'main()::<lambda(const S&, const S&)>' and 'int S::*')
std::sort(v.begin(), v.end(), l<&S::b>);
                              ~^~~~~~

Looks like the template notation for the lambda as parameter for std::sort is taken as a comparison of l less than (<) &S::b>, that wasn't my intention.

How should I pass the template lambda to std::sort function?


Solution

  • You can just do this: std::ranges::sort(v, {}, &S::b).


    Your version doesn't work because l is not a template. It's a class instance with a templated operator().

    &decltype(l)::operator()<&S::b> would compile, but the resulting member pointer is not something that std::sort can work with.

    It will work if you make the lambda static ([](...) static {...}, a C++23 feature), or if you wrap that expression in std::bind_front(..., l) or equivalent.

    Honestly, if you don't want to use std::ranges::sort() for some reason, I'd do this:

    auto l = [](auto ptr)
    {
        return [ptr](const S &a, const S &b)
        {
            return a.*ptr < b.*ptr;
        };
    };
    
    std::sort(v.begin(), v.end(), l(&S::b));
    

    Yes, this results in a stateful comparator (the member pointer isn't embedded into the type), but the syntax looks much nicer, and this should hopefully be optimized away.