Is this legal template lambda syntax?

All standard references below refers to N4659: March 2017 post-Kona working draft/C++17 DIS.


Generic lambdas: a C++14 feature

The canonical way of producing template lambdas seems to be wrapping them in template structs or template functions. C++20 introduces named template params for lambdas, but that's a different syntax (after the capture brackets).

The other answer thoroughly explains what construct the OPs variable template is, whereas this answer addresses the emphasized segment above; namely that generic lambdas is a language feature as of C++14, and not something that is available only as of C++20.

As per [expr.prim.lambda.closure]/3 [extract]:

[...] For a generic lambda, the closure type has a public inline function call operator member template whose template-parameter-list consists of one invented type template-parameter for each occurrence of auto in the lambda's parameter-declaration-clause, in order of appearance. [...]

a generic lambda can be declared as

auto glambda = [](auto a, auto b) { return a < b; };

which is comparable to

struct anon_struct {
    template<typename T, typename U>
    bool operator()(T a, U b) { return a < b; }
}

and not

template<typename T, typename U>
struct anon_struct {
    bool operator()(T a, U b) { return a < b; }
}

which is essential as a single generic lambda object (whose closure type is in fact not a class template but a non-template (non-union) class) can be used to generically invoke its function call operator template for different instantiations of its invented template parameters.

#include <iostream>
#include <ios>

int main() {
    auto gl = [](auto a, auto b) { return a < b; };
    std::cout << std::boolalpha 
        << gl(1, 2) << " "      // true ("<int, int>")
        << gl(3.4, 2.2) << " "  // false ("<double, double>")
        << gl(98, 'a');         // false ("<int, char>")
}

Generic lambdas with an explicit template parameter list: a C++20 feature

As of C++20 we may use an explicit template parameter list when declaring the generic lambdas, as well as offering a sugared syntax for providing explicit template arguments when invoking the generic lambdas.

In C++14 and C++17 the template parameters for a generic lambda can only be declared implicitly as invented type template parameters for each declared auto parameter in the lambda declaration, which has the restrictions that:

  • the invented template parameters can only be type template parameters synthesized (as shown above), and
  • the type template parameters cannot be directly accessed in the body of the lambda, but needs to be extracted using decltype on the respective auto parameter.

Or, as shown with a contrived example:

#include <type_traits>

// C++17 (C++14 if we remove constexpr
//        and use of _v alias template).
auto constexpr cpp17_glambda = 
    // Template parameters cannot be declared
    // explicitly, meaning only type template
    // parameters can be used.
    [](auto a, auto b) 
        // Inventend type template parameters cannot
        // be accessed/used directly.
        -> std::enable_if_t<
             std::is_base_of_v<decltype(a), decltype(b)>> {};

struct Base {};
struct Derived : public Base {};
struct NonDerived {};
struct ConvertsToDerived { operator Derived() { return {}; } };
    
int main() {
    cpp17_glambda(Base{}, Derived{});    // Ok.
    //cpp17_glambda(Base{}, NonDerived{}); // Error.
    
    // Error: second invented type template parameter
    //        inferred to 'ConvertsToDerived'.
    //cpp17_glambda(Base{}, ConvertsToDerived{});
    
    // OK: explicitly specify the types of the invented
    //     type template parameters.
    cpp17_glambda.operator()<Base, Derived>(
        Base{}, ConvertsToDerived{});
}

Now, in C++20, with the introduction of name template parameters for lambdas (as well as requires clauses), the example above can be reduced to:

#include <type_traits>

auto constexpr cpp20_glambda = 
    []<typename T, typename U>(T, U) 
        requires std::is_base_of_v<T, U> { };

struct Base {};
struct Derived : public Base {};
struct NonDerived {};
struct ConvertsToDerived { operator Derived() { return {}; } };

int main() {
    cpp20_glambda(Base{}, Derived{});    // Ok.
    //cpp20_glambda(Base{}, NonDerived{}); // Error.
    
    // Error: second type template parameter
    //        inferred to 'ConvertsToDerived'.
    //cpp20_glambda(Base{}, ConvertsToDerived{});
    
    // OK: explicitly specify the types of the
    //     type template parameters.
    cpp20_glambda.operator()<Base, Derived>(
        Base{}, ConvertsToDerived{});
}

and we can moreover declare lambdas with template parameters that are not necessarily type template parameters:

#include <iostream>
#include <ios>

template<typename T>
struct is_bool_trait {
    static constexpr bool value = false;  
};

template<>
struct is_bool_trait<bool> {
    static constexpr bool value = true;  
};

template<typename T>
struct always_true_trait {
    static constexpr bool value = true;    
};

int main() {
    auto lambda = []<
        template<typename> class TT = is_bool_trait>(auto a) -> bool { 
        if constexpr (!TT<decltype(a)>::value) {
            return true;  // default for non-bool. 
        }
        return a; 
    };
    std::cout << std::boolalpha 
        << lambda(false) << " "                            // false
        << lambda(true) << " "                             // true
        << lambda(0) << " "                                // true
        << lambda(1) << " "                                // true
        << lambda.operator()<always_true_trait>(0) << " "  // false
        << lambda.operator()<always_true_trait>(1);        // true
}

template<bool b>
auto lambda_pred = [] (S const & s) { return s.b == b; };

This is not really a template-lambda, it is rather a variable template which is assigned to a lambda.

It is not equivalent to adding template parameters to the implicitly declared Closure struct which has this lambda as a call operator (the traditional approach):

template<bool b>
struct StructPred { // NOT equivalent to this
    bool operator()(S const & s) { return s.b == b; }
};

struct StructPred { // NOT equivalent to this either
    template<bool b>
    bool operator()(S const & s) { return s.b == b; }
};

It is instead equivalent to creating different Closures depending on the template parameters of the variable. So for the bool example, this would be like choosing between the operator() of one of the following types:

struct StructPred_true {
    bool operator()(S const & s) { return s.b == true; }
}

struct StructPred_false {
    bool operator()(S const & s) { return s.b == false; }
}

This approach won't allow for partial specializations and is thus less powerful. Another reason why this approach might be unpopular is that it doesn't give you easy access to the Closure type(s). StructPred can be worked with explicitly, unlike the anonymous classes StructPred_true and StructPred_false

A template lambda in C++20 would look as follows:

auto lambda = []<bool b>(S const & s){ return s.b == b; };

This is instead equivalent to making the Closure's operator() templated.