Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Function template accept and return different lambdas

Tags:

c++

c++20

I have the function GetThing as follows:

auto GetThing(size_t index, auto&& l1)
{
    return l1;
}
auto GetThing(size_t index, auto&& l1, auto&&... rest)
{
    if (index == 0)
        return l1;
    return GetThing(index - 1, rest...);
}

I want it to be able to work with different lambdas too while being able to handle other types (meaning non-lambdas, non functions, like int, and ...) , such as

std::cout << GetThing(1, 2, 3, 4);   //works, return 3
std::cout << GetThing(1, [] {return 0; }, 
    [] {return 1; }, [] {return 2; }, 
    [] {return 3; } )();             //nope

But the problem here being the lambdas are different type, therefore the recursive function will deduced to incompatible return type, so I seems to have to use std::function like this, but it's ugly.

std::cout << GetThing(1, std::function{ [] {return 0; } }, std::function{ [] {return 1; } }, std::function{ [] {return 2; } }, std::function{ [] {return 3; } })();//works

Any possible way to get around this, for example if there is an overloaded operator() then it automatically enforce the type to be std::function?

EDIT: I am aware of capture-less lambdas can be converted to a function pointer, but how to deduce it that way without std::decay in the template? Because I still want to to be handle other types as references

EDIT2: I receive a few answers utilizing std::variant, and am thinking of that, besides lambda, the parameter types shall be the same, eg. std::variant<int, int, int>. It maybe possible to add overload to GetThing, such that whenstd::variant is holding the same types, it return the thing of that type, otherwise (which is the case of receiving lambdas), returns a std::function

like image 939
sz ppeter Avatar asked Aug 03 '20 08:08

sz ppeter


2 Answers

You may store your functions in an array of variants. This comes with some overhead of course. But this enables to have functions also using captured vars.

This enable to pick a function from such function collection and execute it with given parms as follows:

template < typename ARR_T >
struct Collect
{
    template < typename ... T > 
    Collect( T&&...args  ): arr{std::forward<T>(args)...}{}
    ARR_T arr;
    using VARIANT_T = ARR_T::value_type;
    VARIANT_T& operator[]( size_t index) { return arr[index]; }
};

template < typename ... T > 
Collect( T&& ... args ) -> Collect< std::array< std::variant<T... >, sizeof...(T) >>; 

template < typename C, typename ... PARMS >
auto GetThing( size_t index, C&& c, PARMS&&... parms ) 
{
    return std::visit( [ &parms...]( auto&& func)
                      {
                          return func(std::forward<PARMS>(parms)...);
                      }, c[index]);
}

int main()
{
    std::cout << GetThing( 2, Collect(  []( int, double) {return 0; }, []( int, double) {return 1; }, []( int, double) {return 2; }, []( int, double) {return 3; }), 1,5.6)<< std::endl;

    int y = 8;
    double d = 9.99;

    std::cout << GetThing( 0, Collect(  [y,d]( int, double) {return d*y; }, []( int, double) {return 1.; }, []( int, double) {return 2.; }, []( int, double) {return 3.; }), 1,5.6)<< std::endl;
}



In this case GetThing also take the function parameters for calling the lambda, because the call is using std::visit. If you "only" want to pick the function, you will get the std::variant if you like and can call the function your self.


    auto func = Collect(  []( int i, double d) {return d+i; }, []( int i, double d) {return d*i; }, []( int i, double d) {return d-i; } )[2];
    std::cout << std::visit( []( auto&& f) { return f( 9, 7.77 ); }, func ) << std::endl;
}
like image 132
Klaus Avatar answered Sep 24 '22 00:09

Klaus


You can return a std::variant that contains all input types:

template <typename... Args>
std::variant<std::decay_t<Args>...>
GetThing(std::size_t index, Args&&... args)
{ 
  return [index, t=std::forward_as_tuple(std::forward<Args>(args)...)] 
    <std::size_t... Is>(std::index_sequence<Is...>) { 
    return std::array{ +[](const std::tuple<Args&&...>& t) { 
      return std::variant<std::decay_t<Args>...>{ 
        std::in_place_index<Is>, std::get<Is>(t)}; 
      } ... 
    }[index](t); 
  }(std::index_sequence_for<Args...>{}); 
}

Then you need std::visit to visit your returned value:

for (std::size_t index = 0; index < 4; index++)
  std::visit(
    [](auto&& f) { std::cout << f() << " "; }, 
    GetThing(index, []{return 0;}, []{return 1;}, []{return 2;}, []{return 3;})
  );
like image 41
康桓瑋 Avatar answered Sep 21 '22 00:09

康桓瑋