Registration of arguments acquired using variadic templates and tuples unpacking question.

Hello,

I am currently working on an Event library based on Variadic templates, and I need your expertise to help me optimize my approach of the delayed events.

So here is the point : I added the method "fireLater( time, args )" to my event system (as the class use variadic templates, I can pass 0 to x arguments), but of course I need to register the arguments until the event is really fired.

So I decided to use std::tuple(s) to them all until the delay expire and it's time to really fire the event.

It does work very nice, but I'm not really satisfied, and I would like to know if there is a better way to do this.



Here is a simplified pseudo-code :

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
53
54
55
56
57
58
59
60
61
62
63
64
65

template< unsigned int i >
struct tupleUnpackCall
{
	template< class C, class Tr, class... Args, class... ArgsValue >
	static Tr unpack( C *instance, Tr(C::*func)(Args...), std::tuple<Args...> &tuple, ArgsValue... argsVal)
	{
		tupleUnpackCall< i-1 >::unpack( instance, func, tuple, std::get<i-1>(tuple), argsVal... );
	}
};

template<>
struct tupleUnpackCall<0>
{
	template< class C, class Tr, class... Args, class... ArgsValue >
	static Tr unpack( C *instance, Tr(C::*func)(Args...), std::tuple<Args...> &tuple, ArgsValue... argsVal)
	{
		(instance->*func)( argsVal... );
	}
};

template < class TypeReturn, class... Args>
class EventDispatcher
{
	void fireNow( Args... args )
	{
		// Call for the registered callback functions passing (args...) as argument.
	}
	
	void fireLater( int deliveryTime, Args... args )
	{
		myDelayedList.push_back( std::make_pair(deliveryTime, std::tuple<Args...>{args...}) );			
	}

	void timeUpdate( int currentTime )
	{
		while( (myDelayedList.size() > 0) && (myDelayedList.front().first <= currentTime ) )
		{
			for( /*loop the entire callback list*/ )
				tupleUnpackCall< sizeof...(Args) >::unpack( iterated_target_instance, &iterated_function_pointer, myDelayedList.front().second );
				// is there a better way to unpack the tuple as an argument list of a function ?
			
			myDelayedList.pop_front();
		}
	}

	private:
		std::list<std::pair<int, std::tuple<Args...>>> myDelayedList;
}

int main()
{
	EventDispatcher< bool, int, std::string > myDispatcher;
	
	/* register for function callbacks with function using bool as return type and int & std::string as arguments */
	
	// Immediate dispatch
	myDispatcher.fireNow( 1, "hey 1" ); // fire func( 1, "hey 1")
	
	// Dispatch later
	myDispatcher.fireLater( 10000, 2, "hey 2" );
	
	myDispatcher.update( 100 ); // nothing
	myDispatcher.update( 15000 ); // fire func( 2, "hey 2")
}


(This is a very altered version of the code)

As I said, this work very nice, but for optimization purpose I would like to know if there is a better way to unpack the tuple arguments or to avoid using tuples ?

Thank you.
Last edited on
Use std::bind, std::function and functors instead. I think you'll end up with a much more usable interface.

Hello PanGalactic,

I'm not really fluent with functors, but as you point it as a solution, I think it's time for further reading on this subject. I will search this way for the moment.

Thank you for your help.
After some researches on functors, std::bind and std::function (It's not that easy to find documentation about STD bind and function...), I was able to play with them, but I was unable to register the arguments values into the bind. I can register for the std::function but without the parameters values.

So, does std::bind is supposed to register for the values passed as argument ? Or does I misunderstood the way to use it ?

Because clearly, I'm unable to make the functor call work without passing it parameters values once again.


1
2
3
4
5
6
7
8
9
10
11
12
template<typename ...T_arguments>
class RegisterArgumentsTest 
{
	std::function<bool (T_arguments&...)> functor;

	public:
		RegisterArgumentsTest (bool (*func)(T_arguments&...), T_arguments... arguments) : functor(std::bind(func, arguments...))
		{
			functor();		// Fire test 1 - Does NOT work
			functor(arguments...);	// Fire test 2 - Does work, but i need to pass arguments once again.
		}
};


Fire test 1 return an error, Fire test 2 does fire, but it request for parameters once again... and as I search for a butter way than tuples to store arguments, if it doesn't save them I guess this is not the solution I need.
To make your code work as you expect, I believe this is the only change required:

1
2
3
4
5
6
7
8
9
10
11
template<typename ...T_arguments>
class RegisterArgumentsTest 
{
	std::function<bool (void)> functor;

	public:
		RegisterArgumentsTest (bool (*func)(T_arguments&...), T_arguments... arguments) : functor(std::bind(func, arguments...))
		{
			functor();		// Fire test 1 - Should now work.
		}
};



However, I suggest you try something like this instead:

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
class RegisterFunctorTest 
{
    typedef std::function<bool (void)> function_type;

    function_type functor;
    
public:
    RegisterFunctorTest (const function_type& f) : functor(f)
    {
        functor();		// Fire
    }

};

...

bool myFunction(std::string, int)
{
    ...
}

struct Klass
{
    ...
    bool func(std::string, int) {...}
};

RegisterFunctorTest(std::bind(myFunction, "hello", 1));

Klass k;
RegisterFunctorTest(std::bind(&Klass::func, k, "hello", 2));


Does that make sense? This simplifies the design by requiring the user to use std::bind to create the nullary function before passing it in.


Woaw you are right,

the std::function<bool (void)> functor; instead of std::function<bool (T_arguments&...)> functor; did exactly what I needed.

As for the design, I do not really use the example I provided but that was the simplest way to ask my question without losing people in useless code portions. But I think you are right, your approach does simplify the design, and I will probably use it on another part of my code.

Thank you very much for you help PanGalactic.



For future readers interested in this thread, I had to use std::ref on the class instances passed to std::bind when binding a member method in order to avoid multiple copies and destructor calls. I discovered that each instance binded with std::bind and std::function called 3+1 copy constructors and 3+1 destructors on top of normal ones. But if std::ref is used, there is only 1 default constructor + 1 default destructor called)

1
2
Klass k;
std::function<void(void)> test1 = std::bind(&Klass::func, k, "hello", 2 );

generate 1 constructor call + 4 copy constructor calls + 5 destructor calls of Klass.

1
2
Klass k;
std::function<void(void)> test2 = std::bind(&Klass::func, std::set(k), "hello", 2 );

generate 1 constructor call + 1 destructor call of Klass.

Last edited on
Since you are using C++0x features, if you implement move constructors, 2 of the constructor calls are to move constructors, or 3 if you do:
1
2
Klass k;
std::function<void(void)> test2 = std::bind(&Klass::func, std::move(k), "hello", 2 );
I was not aware of the move C++ technique and Rvalue references (I am almost new to C++ and I learn alone by experimenting, so there is still probably tons of nice functions I am not aware off yet) and this is very interesting to optimize some portions of code, even if I am almost sure this must be used with cautions.

Your answer come right in time as a solution to an optimization problem I was working on right now ... so thank you once again, you taught me some very interesting C++ features.
Last edited on
Topic archived. No new replies allowed.