7

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

sz ppeter
  • 1,698
  • 1
  • 9
  • 21

4 Answers4

3

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;
}
Klaus
  • 24,205
  • 7
  • 58
  • 113
  • Upvoted, very interesting solution. But still can't handle other normal non-lambda/non-function types. – sz ppeter Aug 04 '20 at 00:07
  • @szppeter What kind of "handling" do you want to achieve. If the "thing" is not executable, what you want to do with it? The collect class collects "all", so there is no problem. But my GetThing is calling/executing that object what I thought was the idea. What you want to do with non function non lambda types? – Klaus Aug 04 '20 at 06:31
  • Such as returning n-th int in the parameter list which doesn't involve a function call, that why the function is ``GetThing`` not ``GetLambda`` or any specific. I just want it to return the n-th parameter (beside the ``index``) – sz ppeter Aug 04 '20 at 06:34
  • Put it more clearly, I just want a ``GetThing`` function, that can also be used to get a lambda (which may be converted to ``std::function`` or function pointer or anything that works), if it's ever possible – sz ppeter Aug 04 '20 at 06:41
  • @szppeter That is already done. The Collect class exactly deliver "any" type you put in, encapsulated into a variant. I can't see your problem, because I see it already solved. Can you add some pseudo code to your question with 1) Store data to collection, 2) Pick some element, 3 ) Do something with the found element. Especially the last point is fully undefined. The collect class already collect all data, pick one element and deliver it back. So what is missing? – Klaus Aug 04 '20 at 06:49
  • OK I edited my question. Your answers expects the parameter (``c`` in this case) to be ``Collection`` which will fail for simple case like ``GetThing(1, 2, 3, 4);`` – sz ppeter Aug 04 '20 at 07:01
  • 1
    @szppeter Take Collect(1,2,3,4)[2] and you get std::variant<...> with int and value 3. – Klaus Aug 04 '20 at 07:11
  • This fails if you pass in non-rvalues to `Collect`, as that results in a variant of references, which is illegal. It also creates N variants in an array, some of which get copies of arguments, which seems inefficient; it uses max(k+sizeof(Ts)...) times sizeof...(Ts) memory in automatic storage, and I doubt most optimizers can avoid that. – Yakk - Adam Nevraumont Aug 04 '20 at 17:28
  • [My improvement](https://godbolt.org/z/nvET1r), which unifies the interface of ``GetThing()`` regarding to my ``EDIT2`` – sz ppeter Aug 04 '20 at 17:42
3

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;})
  );
康桓瑋
  • 33,481
  • 5
  • 40
  • 90
2

For captureless lambdas you can use function pointers

#include <utility> // std::forward

auto GetThing(size_t index, auto&& l1)
{
    return  std::forward<decltype(l1)>(l1);
}

auto GetThing(size_t index, auto&& l1, auto&&... rest)
{
    if (index == 0)
        return std::forward<decltype(l1)>(l1);
    return GetThing(index - 1, std::forward<decltype(rest)>(rest)...);
}

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

will work: Demo

Also note that, you need to add + for converting the lambda to function pointer. Read more: A positive lambda: '+[]{}' - What sorcery is this?


Also, in case of lambda with capture you need to use std::function. The above one will not be enough/will not work!

Const
  • 1,306
  • 1
  • 10
  • 26
  • You're not using `std::forward` correctly. (The parameters should be forwarding references, and possibly the return type too.) – HolyBlackCat Aug 03 '20 at 09:11
  • @HolyBlackCat Sorry, I forgot. I have corrected it. Thanks for the correction. – Const Aug 03 '20 at 09:15
  • You might also want to mention that it's `+` that makes it work. Adding `std::forward` is just a general improvement. – HolyBlackCat Aug 03 '20 at 09:20
  • @HolyBlackCat Yes added... Thanks – Const Aug 03 '20 at 09:29
  • 2
    As a minor improvement, you could do the decay to function pointer inside `GetThing`, and then the call site is the same as OP's. [demo](https://godbolt.org/z/janfhx). – cigien Aug 03 '20 at 14:19
2

As solved by @康桓瑋 a variant is what you want when you have a sum type.

Here is what I think is a cleaner solution.

template<std::size_t...Is>
using enumeration = std::variant<std::integral_constant<std::size_t, Is>...>;

An enumeration is a variant of compile time values.

They are a runtime store of a compile time value which can then be used.

template<class T>
struct enum_from_seq;
template<class T>
using enum_from_seq_t = typename enum_from_seq<T>::type;
template<std::size_t...Is>
struct enum_from_seq<std::index_sequence<Is...>>{ using type=enumeration<Is...>; };

template<std::size_t N>
using variant_index_t = enum_from_seq_t<std::make_index_sequence<N>>;

A variant_index_t is an enumeration of 0 to N-1. It can be used to interact with variants.

template<std::size_t N, std::size_t...Is>
variant_index_t<N> make_variant_index_helper( std::size_t I, std::index_sequence<Is...> ) {
  constexpr variant_index_t<N> retvals[] = {
    variant_index_t<N>{ std::integral_constant<std::size_t, Is>{} }...
  };
  return retvals[I];
}

template<std::size_t N>
variant_index_t<N> make_variant_index( std::size_t I ) {
  return make_variant_index_helper<N>( I, std::make_index_sequence<N>{} );
}
//TODO: handle valueless_by_exception:
template<class...Ts>
variant_index_t<sizeof...(Ts)> get_index( std::variant<Ts...>const& v ) {
  return make_variant_index<sizeof...(Ts)>( v.index() );
}

now we can make an enumeration for a variant from a size and a run-time element.

This is annoying machinery, but at least it is generic.

template<class...Ts>
std::variant< std::decay_t<Ts>... > pick( std::size_t I, Ts&&...ts ) {
  using return_type = std::variant< std::decay_t<Ts>... >;

  std::tuple< std::remove_reference_t<Ts>*... > retvals = {std::addressof(ts)...};

  auto index = make_variant_index<sizeof...(Ts)>(I);
        
  return std::visit( [&](auto I)->return_type {
    //TODO: perfect forward here.  If constexpr on the Ith Ts maybe?
    return return_type(std::in_place_index<I>, *std::get<I>(retvals));
  }, index );
}

see, no recursion.

Test code:

for (std::size_t i = 0; i < 5; ++i)
{
    auto x = pick( i, 3.14, 2, "hello", "goodbye", -3.14 );
    std::visit( [](auto&& x) {
        std::cout << x << "\n";
    }, x );
}

Live example.

Output:

3.14
2
hello
goodbye
-3.14
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524