Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Convert template function to generic lambda

I'd like to pass templated functions around as if they were generic lambdas, however this does not work.

#include <iostream>
#include <vector>
#include <tuple>
#include <string>
#include <utility> 


// for_each with std::tuple
// (from https://stackoverflow.com/a/6894436/1583122)
template<std::size_t I = 0, typename FuncT, typename... Tp>
inline typename std::enable_if<I == sizeof...(Tp), void>::type
for_each(std::tuple<Tp...> &, FuncT)
{}

template<std::size_t I = 0, typename FuncT, typename... Tp>
inline typename std::enable_if<I < sizeof...(Tp), void>::type
for_each(std::tuple<Tp...>& t, FuncT f) {
    f(std::get<I>(t));
    for_each<I + 1, FuncT, Tp...>(t, f);
}

// my code
template<class T> auto
print(const std::vector<T>& v) -> void {
    for (const auto& e : v) {
        std::cout << e << "\t";
    }
}

struct print_wrapper {
    template<class T>
    auto operator()(const std::vector<T>& v) {
        print(v);
    }
};

auto print_gen_lambda = [](const auto& v){ print(v); };

auto print_gen_lambda_2 = []<class T>(const std::vector<T>& v){ print(v); }; // proposal P0428R1, gcc extension in c++14/c++17

int main() {
     std::tuple<std::vector<int>,std::vector<double>,std::vector<std::string>> t = { {42,43},{3.14,2.7},{"Hello","World"}};
    for_each(t, print); // case 1: error: template argument deduction/substitution failed: couldn't deduce template parameter 'FuncT'
    for_each(t, print_wrapper()); // case 2: ok
    for_each(t, print_gen_lambda); // case 3: ok
    for_each(t, print_gen_lambda_2); // case 4: ok
}

Note that case 2 and 4 are strictly equivalent. Case 3 is more general but unconstrained (this is a problem for me). I think that case 1 should be treated equivalently to cases 2 and 4 by the language, however this is not the case.

  • Is there a proposal to implicitly convert a template function to a generic constrained lambda (case 2/4)? If no, is there a fundamental language reason that prevents from doing so?
  • As of now, I have to use case 2, which is quite cumbersome.
    • case 4: not c++14-compliant, even if should be standard in c++20, and still not perfect (verbose since you create a lambda that fundamentally does not add any information).
    • case 3: is unconstrained, but I rely (not shown here) on substitution failure for calls to "print" with non-"vector" arguments (P0428R1 mentions this problem). So I guess the subsidiary question is "Can I constrain a generic lambda with some enable_if tricks?"

Is there, in C++14/17/20, a very terse manner to enable the conversion from case 1 to case 2? I am even open to macro hacks.

like image 203
Bérenger Avatar asked Jul 19 '17 10:07

Bérenger


People also ask

Can you template a lambda?

Lambda-expressions are not allowed in unevaluated expressions, template arguments, alias declarations, typedef declarations, and anywhere in a function (or function template) declaration except the function body and the function's default arguments.

What is generic lambdas?

Generic lambdasLambda function parameters can now be auto to let the compiler deduce the type. This generates a lambda type with a templated operator() so that the same lambda object can be invoked with any suitable type and a type-safe function with the right parameter type will be automatically generated.

What is generic lambda in C++?

Generic lambdas were introduced in C++14 . Simply, the closure type defined by the lambda expression will have a templated call operator rather than the regular, non-template call operator of C++11 's lambdas (of course, when auto appears at least once in the parameter list).

What is generic Lambda in C++ with example?

C++ Generic lambdas. Example. c++14. Lambda functions can take arguments of arbitrary types. This allows a lambda to be more generic: auto twice = [](auto x){ return x+x; }; int i = twice(2); // i == 4 std::string s = twice("hello"); // s == "hellohello".

What is the target type of lambda expression?

The functional interface can be generic (template). In this case, the target type of the lambda expression is determined based on the type specified in the reference to that functional interface. For example. Let the generalized functional interface IValue be given

How to wrap a lambda function argument in an identity type?

You can do this by wrapping the function argument in an identity type so that it doesn't fail on trying to match the lambda to std::function (because dependent types are just ignored by type deduction) and giving some other arguments. template <typename T> struct identity { typedef T type; }; template <typename...

How to get the std::function type for lambda function?

It is possible to get the needed std::function type for lambda using derivation, decltype, variadic templates and a few type traits: namespace ambient { template <typename Function> struct function_traits : public function_traits<decltype (&Function::operator ())> {}; template <typename ClassType, typename ReturnType, typename...


1 Answers

Is there, in C++14/17/20, a very terse manner to enable the conversion from case 1 to case 2? I am even open to macro hacks.

Yes.

// C++ requires you to type out the same function body three times to obtain
// SFINAE-friendliness and noexcept-correctness. That's unacceptable.
#define RETURNS(...) noexcept(noexcept(__VA_ARGS__)) \
     -> decltype(__VA_ARGS__){ return __VA_ARGS__; }

// The name of overload sets can be legally used as part of a function call -
// we can use a macro to create a lambda for us that "lifts" the overload set
// into a function object.
#define LIFT(f) [](auto&&... xs) RETURNS(f(::std::forward<decltype(xs)>(xs)...))

You can then say:

for_each(t, LIFT(print)); 

Is there a proposal to implicitly convert a template function to a generic constrained lambda?

Yes, look at P0119 or N3617. Not sure about their status.

like image 97
Vittorio Romeo Avatar answered Sep 25 '22 17:09

Vittorio Romeo