Convert template function to common lambda

I would like to use template functions 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 cases 2 and 4 are strictly equivalent. Case 3 is more general, but unconditional (for me this is a problem). I think that case 1 should be considered equivalent to cases 2 and 4 in terms of language, but this is not so.

  • Is there any suggestion to implicitly convert the template function to a common bounded lambda (case 2/4)? If not, is there a fundamental reason for the language that prevents this?
  • At the moment, I should use case 2, which is rather bulky.
    • case 4: not C ++ 14-compatible, even if it should be standard in C ++ 20 and still not perfect (verbose since you create a lambda that basically does not add any information).
    • Case 3: There are no restrictions, but I rely (not shown here) on a replacement failure for calls to "print" with non-"vector" arguments (P0428R1 mentions this problem). So I guess the supporting question is: "Can I limit the overall lambda with any make_if tricks?"

Is there a very concise way in C ++ 14/17/20 that allows you to convert from case 1 to case 2? I am even open to macros.

+5
source share
2 answers

Is there a very concise way in C ++ 14/17/20 that allows you to convert from case 1 to case 2? I am even open to macros.

Yes.

 // C++ requires you to type out the same function body three times to obtain // SFINAE-friendliness and noexcept-correctness. That 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)...)) 

Then you can say:

 for_each(t, LIFT(print)); 

Is there any suggestion to implicitly convert the template function to a common restricted lambda?

Yes, see P0119 or N3617 . Not sure about their status.

+7
source

Can I limit the overall lambda with some make_if tricks?

If all you want is to limit the parameter types of your common lambda, you can do this with a few function declarations (without a definition) and static_assert (so you get a static_assert message error at compile time in case of failure). No macros at all (they are such C-ish).

This follows a minimal working example:

 #include<vector> #include<type_traits> #include<utility> #include<list> template<template<typename...> class C, typename... A> constexpr std::true_type spec(int, C<A...>); template<template<typename...> class C, template<typename...> class T, typename... A> constexpr std::false_type spec(char, T<A...>); int main() { auto fn = [](auto&& v) { static_assert(decltype(spec<std::vector>(0, std::declval<std::decay_t<decltype(v)>>()))::value, "!"); // ... }; fn(std::vector<int>{}); // fn(std::list<int>{}); //fn(int{}); } 

If you switch the comments to the last lines, static_assert will static_assert error and the compilation will fail. Look at it and launch wandbox .


Side note.

Of course, you can reduce the template here:

 static_assert(decltype(spec<std::vector>(0, std::declval<std::decay_t<decltype(v)>>()))::value, "!"); 

Add a variable template, for example the following:

 template<template<typename...> class C, typename T> constexpr bool match = decltype(spec<C>(0, std::declval<std::decay_t<T>>()))::value; 

Then use it in static_assert s:

 static_assert(match<std::vector, decltype(v)>, "!"); 

Pretty clear, right?


Note.

In C ++ 17, you can further reduce the code needed to do this by defining your lambda as:

 auto fn = [](auto&& v) { if constexpr(match<std::vector, decltype(v)>) { print(v); } }; 

See sample code running on wandbox .

+3
source

Source: https://habr.com/ru/post/1269978/


All Articles