Making a class behave like a lambda .. stuck please help

Hi,

I have the suspicion that every lamda is actually implemented as a class, so
i am taking a lambda and trying to represent as a class, but I am running into problems.

A code example follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
template<typename...functions>
	class Multicall
	{
		std::tuple<functions...> pfs;
	public:
		Multicall(functions...fs) : pfs { make_tuple<fs...>}
		{
		}

		template<typename X>
		void operator() (X x)
		{
			std::initializer_list<int> {
				(fs(x), 0)...
			};
		}
	};


which corresponds to the following lambda

1
2
3
4
5
6
7
8
9
10
11
	template<typename ...functions>
	auto multicall(functions...fs)
	{
		return [=](auto x)
		{
			(void)std::initializer_list<int>{
				((void)fs(x), 0)...
			};
		};
	}


So i don;t know how to populate the tuple with the types passed to Multicall ctor.


Help will be appreciated!

Juan
First of all, lambdas is just a function. Anything that you can do with lambdas, you can do with plain functions, vice versa.

Also you should avoid using "capture all" captures, especially the copy version (unless there is a actual reason for it?) [=].

Overall, you can pass a class function as a plain function if you use std::bind, since class functions have an extra first parameter which is the pointer called "this". But you do not need to use the overload operator().

I am assuming that your code is compile-able, but since I noticed that there are some obvious errors in your code, here's the first google link I think will put you in the right direction, but it is doing the exact opposite that you are trying to do (tuple of args instead of functions). https://stackoverflow.com/questions/36612596/tuple-to-parameter-pack
My question is ill phrased -- what I need is how to get a parameter pack received in a constructor into a tuple... That's it.

Or put another way, where and how can I store the parameter pack received on constructor so I can access it from the operator()?


Thanks for your help!

Juan
I was able to make it work by removing make_tuple and using recursion to call each element in the tuple (a tuple is not a parameter pack so you can't simply use ... to expand it).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
template<typename...functions>
class Multicall
{
	std::tuple<functions...> pfs;
public:
	Multicall(functions...fs) : pfs { fs... }
	{
	}

	template<typename X>
	void operator() (X x)
	{
		invoke_pfs<0>(x);
	}
private:
	template<std::size_t I, typename X>
	void invoke_pfs(X x)
	{
		std::get<I>(pfs)(x);
		if constexpr (I + 1 < std::tuple_size<decltype(pfs)>())
		{
			invoke_pfs<I + 1>(x);
		}
	}
};


I'm by no means an expert so there are probably better ways to write this. If x is expensive to copy you might want to pass it by reference to avoid unnecessary copies.
Last edited on
Something like this (hadn't seen Peter87's post when I made this):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <iostream>
#include <tuple>

template < typename... FN > struct multi_call
{
    multi_call() = delete ;
    explicit multi_call( FN... fn ) : functions{ fn... } {}

    template < typename... ARGS >
    void operator() ( ARGS&&... args ) const
    {
        call< void, std::tuple_size< decltype(functions) >::value - 1, std::tuple<FN...> >
                    { functions, std::forward<ARGS>(args)... } ;
    }

    private:

        const std::tuple<FN...> functions ;

        /////////// helper template to extract tuple elements and call ////////////
        template < typename, std::size_t, typename... > struct call ;

        template < typename T, std::size_t N, typename... ITEMS >
        struct call< T, N, std::tuple<ITEMS...> > : call< T, N-1, std::tuple<ITEMS...> >
        {
           template < typename... ARGS >
           call( const std::tuple<ITEMS...>& tup, ARGS&&... args )
               : call< T, N-1, std::tuple<ITEMS...> >( tup, std::forward<ARGS>(args)... )
           { std::get<N>(tup)( std::forward<ARGS>(args)... ) ; }
        };

        template < typename T, typename... ITEMS >
        struct call< T, 0, std::tuple<ITEMS...> >
        {
           template < typename... ARGS >
           call( const std::tuple<ITEMS...>& tup, ARGS&&... args )
           { std::get<0>(tup)( std::forward<ARGS>(args)... ) ; }
        };
};

int main()
{
    const multi_call mc
    {
        [] ( int, int, int ) { std::cout << "one\n" ; },
        [] ( double, short, char ) { std::cout << "two\n" ; },
        [] ( long, double, int ) { std::cout << "three\n" ; },
        [] ( char, char, char ) { std::cout << "four\n" ; }
    };

    mc( 'a', 'b', 'c' ) ;
}

http://coliru.stacked-crooked.com/a/52cb5c2f92541ceb
Last edited on
First of all, lambdas is just a function. Anything that you can do with lambdas, you can do with plain functions, vice versa

Not quite true; or at least, not neatly. For example:
1
2
3
4
5
6
int a;
std::cin >> a;

// how do you do this as a function? No way to capture `a´
auto x = [a](int y){ return a + y; };
std::cout << x(10) << std::endl;


As for the OP: in C++17, you can use std::apply:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template <typename ...Fs>
class Multicall {
public:
    Multicall(Fs&&... fs) : pfs { fs... } {}
    
    template <typename X>
    void operator() (X&& x) {
        std::apply([x](auto&&... f) { 
            (static_cast<void>(std::forward<decltype(f)>(f)(x)), ...); 
        }, pfs);
    }
    
private:
    std::tuple<Fs...> pfs;
};

http://coliru.stacked-crooked.com/a/57daf9e8490a06ef
Last edited on
Hi and thanks for all these possible solutions!!

The only one I do not understand is TwilightSpectre. The docs on apply say:

Invoke the Callable object f with a tuple of arguments.

Whereas here I am trying to do the reverse: invoke each element of a tuple passing it one parameter. So if the tuple contains functions twice or multiplyBy10, these two functions are the ones I need to execute with a common value, say x, as argument.

I am also not clear on the meaning of (..., f(x)). Is this a pack expansion? What is its role?

Regards,
Juan
It may make a little more sense if I break it up a little.

The callable f in this case is the lambda I'm providing as the first argument: [x](auto&&... f) { /* ... */ }. It just seems confusing because the parameters it's taking I've named f (for "Function").

So, on execution I have a lambda which captures x, the argument passed to operator(); and that takes every function in the tuple pfs, as a parameter pack called f.

The (f(x), ...) is a fold expression (new in C++17). See https://en.cppreference.com/w/cpp/language/fold

Also looking back on it, I don't quite pass x around properly (there could be some unnecessary copies: capturing it by reference and std::forwarding it to the function might be better).
Your solution @TwilightSpectre, works as long as one passes lambdas into the tuple but fails to compile if one passes the name of an ordinary function to be placed in the tuple. The error states:


std::_Tuple_val<_Ty>::_Val': a member of a class template cannot acquire a function type
1>c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.14.26428\include\tuple(191,1): error C2207: 	_Ty _Val;


Regards,
Juan
Just my 1 cent worth, in a room full of giants :+)

JUAN DENT wrote:
Your solution @TwilightSpectre, works as long as one passes lambdas into the tuple but fails to compile if one passes the name of an ordinary function to be placed in the tuple.


Can you declare the tuple as being std::function's, it represents any callable target?

http://en.cppreference.com/w/cpp/utility/functional/function
Ah yeah, my bad! I forgot that you can't pass functions as references to the tuple constructor, since it's impossible to declare a variable of type int(int), for example, which was breaking the tuple declaration.

You can solve that just by removing the reference from the constructor. Here's a link to a working version: http://coliru.stacked-crooked.com/a/f88a42499dbb58a5
Topic archived. No new replies allowed.