• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

Pretty Cool Variadic Functions ^)^

Status
Not open for further replies.
Level 31
Joined
Jul 10, 2007
Messages
6,306
Code:
#pragma once

#include <vector>
#include <functional>
#include <stack>
#include <mutex>
#include <atomic>

template<typename... Params>
class Event
{
	struct Callback
	{
		int id;
		int type;
		void(*callback)(Params...);
		Event* event;
		std::function<void(Params...)> callbackEx;
	}; //Callback

	std::vector<Callback> callback;
	std::stack<int> recycler;
	std::mutex key;
	int id;
	std::atomic<bool> reversed;

	int getId()
	{
		int id = 0;

		if (recycler.empty())
		{
			id = ++this->id;
		} //if
		else
		{
			id = recycler.top();
			recycler.pop();
		} //else

		return id;
	} //getId

	void recycle(const int& id)
	{
		recycler.push(id);
	} //recycle

public:

	Event()
	{
		id = 0;
		reversed = false;
	} //Event

	inline void reverse()
	{
		reversed = !reversed;
	} //reverse

	int add(std::function<void(Params...)> f)
	{
		//breaks
		key.lock();
		int id = getId();

		this->callback.push_back({ id, 1, NULL, NULL, f });

		key.unlock();

		return id;
	} //add

	int add(void(*f)(Params...))
	{
		key.lock();

		int id = getId();

		callback.push_back({ id, 2, f });

		key.unlock();

		return id;
	} //add

	int add(Event& event)
	{
		key.lock();

		int id = getId();

		callback.push_back({ id, 3, NULL, &event });

		key.unlock();

		return id;
	} //add

	int add(Event* event)
	{
		key.lock();

		int id = getId();

		callback.push_back({ id, 3, NULL, event });

		key.unlock();

		return id;
	} //add

	Event& operator-=(int id)
	{
		key.lock();

		auto i = callback.begin();

		while (i != callback.end())
		{
			if (i->id == id)
			{
				callback.erase(i);
				recycle(id);

				break;
			} //if

			++i;
		} //for

		key.unlock();

		return *this;
	} //-=

	Event& operator=(Event& other)
	{
		return other;
	} //-=

	void operator()(Params... params)
	{
		int id;

		if (reversed)
		{
			key.lock();
			int i = callback.size() - 1;
			key.unlock();

			for (; i >= 0; --i)
			{
				key.lock();
				Callback& node = callback[i];
				key.unlock();

				id = node.id;

				switch (node.type)
				{
				case 1:
					node.callbackEx(params...);
					break;
				case 2:
					node.callback(params...);
					break;
				case 3:
					(*(node.event))(params...);
					break;
				default:
					break;
				} //switch
			} //for
		} //if
		else
		{
			key.lock();
			for (int i = 0; i < callback.size(); ++i)
			{
				Callback& node = callback[i];
				key.unlock();

				id = node.id;

				switch (node.type)
				{
				case 1:
					node.callbackEx(params...);
					break;
				case 2:
					node.callback(params...);
					break;
				case 3:
					(*(node.event))(params...);
					break;
				default:
					break;
				} //switch

				key.lock();
				if (i < callback.size() && id != node.id)
				{
					--i;
				} //if
			} //for
			key.unlock();
		} //else
	} //()

	void clear()
	{
		recycler.empty();
		id = 0;
		callback.empty();
	} //clear
}; //Event

template<>
class Event < void >
{
	struct Callback
	{
		int id;
		int type;
		void(*callback)();
		Event* event;
		std::function<void()> callbackEx;
	}; //Callback

	std::vector<Callback> callback;
	std::stack<int> recycler;
	int id;
	std::mutex key;
	std::atomic<bool> reversed;

	int getId()
	{
		if (recycler.empty())
		{
			return ++id;
		} //if

		int id = recycler.top();
		recycler.pop();

		return id;
	} //getId

	void recycle(const int& id)
	{
		recycler.push(id);
	} //recycle

public:

	Event()
	{
		id = 0;
		reversed = false;
	} //Event

	inline void reverse()
	{
		reversed = !reversed;
	} //reverse

	int add(std::function<void()> f)
	{
		key.lock();
		int id = getId();

		this->callback.push_back({ id, 1, NULL, NULL, f });
		key.unlock();

		return id;
	} //add

	int add(void(*f)())
	{
		key.lock();
		int id = getId();

		callback.push_back({ id, 2, f });
		key.unlock();

		return id;
	} //add

	int add(Event& event)
	{
		key.lock();
		int id = getId();

		callback.push_back({ id, 3, NULL, &event });
		key.unlock();

		return id;
	} //add

	int add(Event* event)
	{
		key.lock();
		int id = getId();

		callback.push_back({ id, 3, NULL, event });
		key.unlock();

		key.unlock();
		return id;
	} //add

	Event& operator-=(int id)
	{
		key.lock();
		auto i = callback.begin();

		while (i != callback.end())
		{
			if (i->id == id)
			{
				callback.erase(i);
				recycle(id);

				break;
			} //if

			++i;
		} //for

		key.unlock();

		return *this;
	} //-=

	Event& operator=(Event& other)
	{
		return other;
	} //-=

	void operator()()
	{
		int id;

		if (reversed)
		{
			key.lock();
			int i = callback.size() - 1;
			key.unlock();

			for (; i >= 0; --i)
			{
				key.lock();
				Callback& node = callback[i];
				key.unlock();

				id = node.id;

				switch (node.type)
				{
				case 1:
					node.callbackEx();
					break;
				case 2:
					node.callback();
					break;
				case 3:
					(*(node.event))();
					break;
				default:
					break;
				} //switch
			} //for
		} //if
		else
		{
			key.lock();
			for (int i = 0; i < callback.size(); ++i)
			{
				Callback& node = callback[i];
				key.unlock();

				id = node.id;

				switch (node.type)
				{
				case 1:
					node.callbackEx();
					break;
				case 2:
					node.callback();
					break;
				case 3:
					(*(node.event))();
					break;
				default:
					break;
				} //switch

				key.lock();
				if (i < callback.size() && id != node.id)
				{
					--i;
				} //if
			} //for
			key.unlock();
		} //else
	} //()

	void clear()
	{
		recycler.empty();
		id = 0;
		callback.empty();
	} //clear
}; //Event

Code:
#include <iostream>

#include "event.h"

using namespace std;

void test();

int main(int argc, char** argv)
{
	test();

	return 0;
} //main

void meh(const int& a, const int& b)
{
	cout << "test meh" << a << b << endl;
} //meh

void moo(const int& a, const int& b)
{
	cout << "test moo" << a << b << endl;
} //meh

struct Test
{
	void test(const int& a, const int& b)
	{
		cout << "test test" << a << b << endl;
	}
}; //Test

void ehh()
{
	cout << "ehhh" << endl;
}

void test()
{
	Event<const int&, const int&> event;
	Test tester;
	//event.reverse();
	event.add([&tester, &event](const int& a, const int& b) { tester.test(a, b); event -= 1; });
	event.add([&tester, &event](const int& a, const int& b) { tester.test(a, b); event -= 2; });
	event.add(meh);
	event.add([&tester, &event](const int& a, const int& b) { tester.test(a, b); event -= 4; });
	event.add(meh);
	event.add([&tester, &event](const int& a, const int& b) { tester.test(a, b); event -= 6; });
	event.add(meh);
	event.add(meh);
	event.add([&tester, &event](const int& a, const int& b) { tester.test(a, b); event -= 9; });
	event.add([&tester, &event](const int& a, const int& b) { tester.test(a, b); event -= 10; });

	event(1, 2);
	cout << "--------------------------" << endl;
	event(1, 2);
} //test
 
Last edited:

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,198
The problem with variable arguments is the very fact that they are variable. It introduces both memory management (in this case stack) and bounding problems.

Passing a large list of arguments will likely not be very efficient as they will require a considerable number of memory copies (as they will most certainly not fit inside registers! Which are some times used for internally linked code by compilers). It is often more efficient to pass a list of arguments as a reference to some area of memory as both approaches need dynamic memory lookup except in the second case only the reference is moved up the stack as opposed to all the elements.

The second problem is that there are considerable bounding issues. Especially if the arguments are coming from some form of data structure (some languages support this such as Java). You can end up with a pretty insane number of arguments being passed into the function which may or may not result in sensible behaviour (such as the possibility of stack overflows, poor performance etc). Worse is that the input argument might be completely valid.

Sure you could use it as some way of dynamic input queue for arguments to callbacks however there are also other approaches, usually involving explicit argument declaration in the bind methods. Too much dynamic behaviour is bad as it becomes more difficult for the compiler to optimize and also less readable as the methods have less strong bounds.

In the case of events, usually they are given very strict types and behaviour. For example, you can only bind a damage response function as a damage response event which can only be attached to a damage response event source. Trying to attach an attack response event or keyboard input event to a damage response event source would then throw a compile time error.
 

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,198
Reference to where? The stack? Since it is a reference to the stack that means that the caller function needs to build a list of arguments in the stack. This means that nesting them might require a copy if modifications are done to prevent unexpected behaviour after the return.

In the case of Java they are done by allocating an array object and passing a reference to that. This means that you could pipe up arguments up a call tree with minimal overhead. However that is simple passing of arrays. The case that is key is when you have static arguments being passed to variable argument function and how that is dealt with. Passing the arguments by reference in that case is not possible so they will have to be passed by value. Since there is no bounds to the number of static arguments defined all kinds of issues can occur, especially with optimization. I believe compilers will try and define separate function instance for different argument numbers as that allows for optimizations such as loop unwinding and removal of the dynamic behaviour overhead. However it must also support the case of a pure dynamic argument call. The result is a lot of code to cover your ambiguity.

The other half of the problem is processing the arguments is often logically not very efficient. The minimum complexity would be O(n) where n is the argument number. The function might become non-trivial if memory allocation is required to handle the dynamic content (such as placing it in a list). The big question to ask is if this is necessary for the task at hand? Often a fixed argument function is a lot easier to write, more readable and can provide the same functionality.
 

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,198
All arguments are passed by the thread stack. Any memory used to generate or pass the arguments must also come from the stack or heap memory belonging to the thread. As such for variable argument functions there are two approaches.

Java's array approach where it allocates an array object on the heap, places all elements in it and passes as a reference to the function. This incurs the penalty of dynamic memory allocation as well as cache misses (as chances are the area used for allocation is not part of the cache set).

The other approach is to allocate elements on the thread stack in a dynamic way, after all push and pull are dynamic. This is faster than the above approach as no complex dynamic memory allocation on the heap is needed (only simple push/pop are used) as well as since the top of the stack is often manipulated it will almost certainly be in cache so fewer cache misses. The disadvantage is the possibility of stack overflow error terminating the thread.

The problem still remains with dealing with a variable number of arguments such as the number of arguments given, passing them and resolving them. There is also the inherit O(n) speed from whatever function logic is used to process all arguments. Often it is better to have functions with fixed arguments to avoid these computational complexities and create more efficient code.

Generally variable argument use should be kept as a convenience for passing multiple separate data elements to some processor function. The nature of such a function is O(n) or worse in all cases as it is always processing some form of list. By using variable arguments in this case you save yourself having to wrap all elements into a collection of sorts and passing that (the compiler does that automatically) which is more readable. The most famous example is the formatted print statement (eg printf) which takes any number of arguments to correspond to substitutions in the input string. However it should be noted that in C/C++ that function is often a special compiler case with unusual argument passing behaviour (such as resolving if the arguments do not match up to the number of needed substitutions or type of substitution).
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
The problem still remains with dealing with a variable number of arguments such as the number of arguments given, passing them and resolving them. There is also the inherit O(n) speed from whatever function logic is used to process all arguments. Often it is better to have functions with fixed arguments to avoid these computational complexities and create more efficient code.

Variadic templates have zero overhead in C++ and are completly safe to use, you know that right?

Best example is std::printf (not the normal C printf).


Reference to where? The stack? Since it is a reference to the stack that means that the caller function needs to build a list of arguments in the stack. This means that nesting them might require a copy if modifications are done to prevent unexpected behaviour after the return.

Its an rvalue reference. And if you need copys, use copys. If you need references, use references. If you just want to safe performance by not making unnecceary copys, use a const reference. For example this:

C++:
void f1(string bound, int i, int i2)
{
    cout << bound << ", " << i << ", " << i2 << endl;
} //f1

should be of course

C++:
void f1(std::string const &bound, int i, int i2)
{
    std::cout << bound << ", " << i << ", " << i2 << '\n';
} //f1

If you get unexpected behavior after the return you chose a wrong design. But that has absolutly nothing to do with the amount of arguments and is your own fault then.


@Code: Don't use raw pointers, dont use global using namespace, don't use manual memory managment, use iterators instead of indexing, use size_t instead of int when refering to a container size, use const & instead of passing by value, use nullptr instead of NULL. Learn about the rule of 3 (or better rule of 5 in C++11). Never ever use raw new or delete. And this doesn't even compile:

C++:
function<void(Params...)> actions[length];

as its not standard conformant. Also never use raw arrays, use std::vector or std::array. Also your code is not exception safe (what happens if easy_bind throws?).


Appart from that you can have the same functionality with lambdas which also don't involve any overhead:

C++:
#include <iostream>
#include <string>

template <typename callable_type>
void call_two_int(callable_type callable, int i1, int i2)
{
	callable(i1, i2);
}


void f1(int i1, int i2) { std::cout << i1 << ", " << i2 << '\n'; }
void f2(std::string const &str, int i1, int i2) { std::cout << str << ", " << i1 << ", " << i2 << '\n'; }
template <typename T> void f3(int i1, T const &t, int i2) { std::cout << i1 << ", " << t << ", " << i2 << '\n'; }

int main()
{
	auto foo2 = [](int i1, int i2) { f2("hello world", i1, i2); }; // bind f2
	auto foo3 = [](int i1, int i2) { f3(i1, 2.5, i2); }; // bind f3

	call_two_int(f1, 1, 2); // 1, 2
	call_two_int(foo2, 1, 2); // hello world, 1, 2
	call_two_int(foo3, 1, 2); // 1, 2.5, 2
}

Sorry to say this, but the code you posted looks more like "C with classes" and is seriously flawed.
 
Last edited:
Level 14
Joined
Dec 12, 2012
Messages
1,007
In that case you can still write an overload that takes a rvalue reference. If I just want to print (or read) a string it doesn't make sense to pass by value, just take a const &.

The problem here is especially that many things are mixed. An "Event" library should only provide this: Events. But this one here does Events, binding, memory managment and implements its own list - that would be at least 4 different librarys (which of course already exist in the stl).

EDIT:

I thought Nestharus was talking about variable arguments not some template functionality. Man his posts make even less sense than they used to.

Yes, for example

C++:
#include <iostream>

template <typename T>
void display(T last) // Overload for the last call to display which only takes one argument
{
	std::cout << last; // Output that last argument
}

template <typename T, typename ...Args>
void display(T current, Args ...rest) // Takes a variable amount of arguments at compile time
{
	std::cout << current << ", "; // Outputs the current argument
	display(rest...); // Instanciates a new overload of display with one argument less (the ...operator unrolls the parameter pack)
}


int main()
{
	display("hello", 2.0, 4, 'a'); // hello, 2.0, 4, a
}

gets compiled (based on the argument types of the call to display("hello", 2.0, 4, 'a')) to something like

C++:
#include <iostream>

void display(const char* first, double second, int third, char fourth)
{
    // After inlining out the recursive calls
    std::cout << first << ", " << second << ", " << third << ", " << fourth; 
}

int main()
{
	display("hello", 2.0, 4, 'a'); // hello, 2.0, 4, a
}

by recursivly instanciating itself.
 
Last edited:
Level 14
Joined
Dec 12, 2012
Messages
1,007
That would be only after optimization (maybe), if it takes the compilation literally it would become a recursive call. This is important otherwise the code is impossible to debug (it will not know where it is).

Yes, as mentioned in the comment // After inlining out the recursive calls. And inlining only happens in Release Mode. If you want to Debug, compile in Debug Mode, then the recursion will be maintained.

it will actually not inline into one body, but it will create one function for each function with unique arguments. I dont know if there exists such optimization even

Of course such optimizations exist, though they might be compiler dependent. But this is something even very bad compilers easily can since many years.

In the example here actually everything gets inlines out. Just check it out: Show Assembly and compile with -O3 (g++4.8). No recursive calls and no runtime overhead left.
 

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,198
Of course such optimizations exist, though they might be compiler dependent. But this is something even very bad compilers easily can since many years.

In the example here actually everything gets inlines out. Just check it out: Show Assembly and compile with -O3 (g++4.8). No recursive calls and no runtime overhead left.
It might also help to specify them as "inline" functions. Or declare them in a header which automatically assumes inline. Otherwise there is a chance that it will not inline if the function is linked into the program (such as if it was an interface for a standard library).
 

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,198
this is not true, there is nothing that says global functions are inline-requested by default. Also nonstatic or noninline global functions in header files are bane of everyone's existance, because if you include the header it will create linker errors
This makes no sense.

Maybe you are not familiar with the keyword "inline" in C++. I understand that compilers more and more ignore it however it is meant to try and force the compiler to in lining the code when not building a debug build (or at least raise the chance it does so). It is useful for external linking because it builds into the object code the necessary information for inlining to occur. Inlining across object files has always been notoriously hard for compilers.

Header functions/methods do not suffer from external linking because they are always present inside the compilation unit as they have to be imported. As such they are considerably more likely to inline.
 

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,198
but it is not implicit when function is global inside header
Yet I read this searching the internet...
•Any function defined right in the class definition is implicitly marked inline
And here it is again...
The only difference between defining a member function completely within the class definition or to just include its declaration in the function and define it later outside the class, is that in the first case the function is automatically considered an inline member function by the compiler, while in the second it is a normal (not-inline) class member function. This causes no differences in behavior, but only on possible compiler optimizations.
Maybe you are referring to C style functions outside classes by "function is global", which are seldom used with regard to C++ programming (outside of the single main method as entry to the program and a few, often compiler specific external entry functions for fatal errors etc) as it defeats the purpose of object orientated programming. Such functions should be static members of an abstract or singleton utility class which as members of classes are subject to implicit inline if the body is declared with the definition.
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
Maybe you are referring to C style functions outside classes by "function is global", which are seldom used with regard to C++ programming (outside of the single main method as entry to the program and a few, often compiler specific external entry functions for fatal errors etc) as it defeats the purpose of object orientated programming. Such functions should be static members of an abstract or singleton utility class which as members of classes are subject to implicit inline if the body is declared with the definition.

Disagree ;)

C++ is a multi-paradigm language and functions outside classes are (and should, if appropriated) be used often and as default.

There are numerous examples in the stl (std::sort, std::accumulate, std::begin, std::end, ...) or in boost (boost::bind, boost::forward, ...) just to name some random ones. Also operator functions are usually defined as free functions like this:


C++:
struct myclass { int value; };

std::ostream &operator<<(std::ostream &stream, myclass const &my)
{
    stream << my.value;
    return stream;
}

because that way you can easily declare a left-hand side of the corresponding operator without requiring the function to be declared as friends. Also the constructor wrappers for template types like tuple (make_tuple(args)) are always free functions, and so on and so on.

This has nothing to do with defeating the purpose of OOP, why should a function like std::sort which doesn't have any internal state, be member of a class (and by that, suggesting it would have a state)? OOP as an end in itself doesn't really make sense. If I don't need an "object", I don't use one.

Free functions are widely used in C++ and much better than having static member functions.
 
Last edited:
Level 23
Joined
Apr 16, 2012
Messages
4,041
please re-read what you said. Nowhere did you say inside class, you said "declare them in header"

also, no functions appart from main? You should check the few dozens in C++ standard. Not everything is, should or deserves to be enclosed within struct/class
 

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,198
C++ is a multi-paradigm language and functions outside classes are (and should, if appropriated) be used often and as default.

There are numerous examples in the stl (std::sort, std::accumulate, std::begin, std::end, ...) or in boost (boost::bind, boost::forward, ...) just to name some random ones. Also operator functions are usually defined as free functions like this:
That is using a namespace like a class however. The reason it is done with the standard libraries is that they are a core part of the language (as they are defined by the standard as standard libraries) so not normally considered as actual programing software (they come with your compiler, and only concern of compiler programmers). Obviously nothing stops you writing your own such packages however most of your C++ code will be classes which are subject to automatic inlining in the headers.

In Java namespace feature is not available as everything has to be in classes (which can then be in various "packages"). Many other languages are also like that. This is why patterns such as singleton and utility classes exist since they declare global functions in an object orientated way. This is not just about C++ since he did show a JASS example of what he thought the syntax could look like.

This has nothing to do with defeating the purpose of OOP, why should a function like std::sort which doesn't have any internal state, be member of a class (and by that, suggesting it would have a state)? OOP as an end in itself doesn't really make sense. If I don't need an "object", I don't use one.
Because in a utility class they are static anyway so do not need an instance. In java such functions are declared inside an abstract class as static so that the class never has an instance and the functions do not use any instance overhead.

Utility classes are often useful to be singletons as the underlying logic could then be based on various utility classes. For example a generic sort algorthim could be implemented with different sort implementations and you can choose which your program uses by instantiating a constant singleton and using that to get the sort algorthim. Since the instance is constant and declared at compile time it should be able to inline the virtual function calls necessary to produce code of approximately equal efficiency.

In the end it is up to the project management to choose what the style rules are. Any way is right as long as it fits the required style.

Nowhere did you say inside class, you said "declare them in header"
Nowhere did I say "not inside a class" either. I have also argued that I assumed everything was inside a class as it often is in object orientated programming.

You should check the few dozens in C++ standard
Those are exempt from standard OOP rules since they are part of the language as argued above. You must also remember that they come from a C, non OOP, background due to how the language evolved. Even if they only exist in C++ they inherited the style to some extent as it would not make sense if they did not match.
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
That is using a namespace like a class however. The reason it is done with the standard libraries is that they are a core part of the language (as they are defined by the standard as standard libraries) so not normally considered as actual programing software (they come with your compiler, and only concern of compiler programmers). Obviously nothing stops you writing your own such packages however most of your C++ code will be classes which are subject to automatic inlining in the headers.

...

Those are exempt from standard OOP rules since they are part of the language as argued above. You must also remember that they come from a C, non OOP, background due to how the language evolved. Even if they only exist in C++ they inherited the style to some extent as it would not make sense if they did not match.

No, this is just wrong.

There are 100 more examples of libraries with a huge amount of free functions, this has absolutly nothing to do with the STL being a core of the language. Want examples? Just look at librarys like boost, wxWidgets, ACE, Scintilla and so on, just to name a few of them. Basically every C++ library uses free functions.

Whenever you write an algorithm that you want to be extended with arbitrary user types you have to use free functions, there is no other option. Best example is std::begin and std::end, which are just there to provide an overloadable interface for user defined iterators.

And a namespace is fundamentally different to a class.

No one uses classes because of inlining, thats a weird assumption.
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
Nowhere did I say "not inside a class" either. I have also argued that I assumed everything was inside a class as it often is in object orientated programming.

compiler will not assume your function is in class if you didnt say otherwise ...


Also SFML even has freestanding functions as well. As LFH said, it is multiparadigm langauge, not something that forces you to write OOP code(*cough* *cough* Java)

Also in C++ invoking static member functions is not nice from syntactic point of view, it is a lot easier to type std::bind(...) than calling something like std::Wrapper::bind(...)(*cough* *cough* boost namespaces are like that too)
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
Ehh... the point of this was to do events like in wc3. I'm very partial to systems that totally run off of events now =).


If I want to be an observer to a class, how do I do this? Well, the class can have a registration method or something, a list, and then when the event occurs, iterate over the list and fire the functions. It can take an std::function or a function pointer, whatever.

Do I really want to recode this over and over again? The list... the function calling, etc? no, I don't, hence I made this, lol...

Another way is to have the class implement an interface and then store the class itself in the other class. This is less flexible and once again requires a list and so forth >.<.


I also updated this library to not do double iterations on execution + some other cool things.


That is the purpose of this library. Sure, you can do it other ways, but those other ways = more code, haha.

Also, typically, when I do an event, I always use const refs as parameters

Code:
virtual void onTick(const long double& delta) { }

The code I put up here was quick test code, which is why it was using std. Regular version doesn't use std.

I do not use an STL List unless I don't care to remove elements in the middle of the list. STL Lists are O(n) for removal. That ain't cool, haha. Also, iterating over the list will destroy the iteration if removing the current iterator, so that's also fail. I guess you can store the next and all that jazz, but iterators also have some extra overhead compared to a regular loop. I dunno, I only use iterators when I only care about convenience or when the iteration is nasty, like iterating over a general tree.

so I specifically used my own Linked List for O(1) removal instead of O(n). It gives me flexibility to remove it during execution of the event too with no worries.


So the primary point of events is to not store a single function. It is to store a set of functions and execute that set. I code everything like I'm coding Triggers from wc3 now, haha. Outsiders are always totally lost, but using this stuff and some other things, I've come up with some of the most flexible designs I have ever seen o_O, haha. Nothing's even loosely coupled, it's just floating in space.



So in finality, I like events. Whenever something does something, I want it to tell everyone listening in what it did. I don't like to execute specific functions and link stuff up by hand. When I'm done with a class, I'm done with it forever. If I want it to have a relationship with another class, I define that relationship from the other class externally via events. From here, I can build on top of the system layer by layer until I have something epic ^_^. This class automates all of that for me. Instead of having to make 1 list of functions and so on for every single event, including registration functions etc (really annoying), I just declare the variable and I'm done. Seems pretty useful, no?
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
If I want to be an observer to a class, how do I do this? Well, the class can have a registration method or something, a list, and then when the event occurs, iterate over the list and fire the functions. It can take an std::function or a function pointer, whatever.

Do I really want to recode this over and over again? The list... the function calling, etc? no, I don't, hence I made this, lol...

That is the purpose of this library. Sure, you can do it other ways, but those other ways = more code, haha.

More code? Lol for such little functionality, this "library" is way to big. Of course you don't wan't to recode all this again, because 90% of the stuff is already there as mentioned. However you do manual memory management (which is a no-go) and implement your own list so you actually do recode a lot of (useless) stuff and even in a very bad way.

The code I put up here was quick test code, which is why it was using std. Regular version doesn't use std.

Regular version is even worse then. C++ Code should use the STL as much as possible, it is part of the language for a reason.

Also, typically, when I do an event, I always use const refs as parameters

Unnecessary (and potentially even slower!) for built-in types like in this example.

I do not use an STL List unless I don't care to remove elements in the middle of the list. STL Lists are O(n) for removal. That ain't cool, haha.

so I specifically used my own Linked List for O(1) removal instead of O(n). It gives me flexibility to remove it during execution of the event too with no worries.

Nonsense. You really think your in-3-minute-made raw-pointer list with manual memory management is better than the one of the STL? This is just pure ignorance. Of course element removal of std::list is O(1). Check std::list::erase.


So the primary point of events is to not store a single function. It is to store a set of functions and execute that set. I code everything like I'm coding Triggers from wc3 now, haha.

And thats a big mistake. This is C++ and not vJass. This event stuff could be just done like this:


C++:
#include <vector>

template <typename callback_type, typename container_type = std::vector<callback_type>>
class event_handler
{
	container_type callbacks;

public:
	size_t add_callback(callback_type callback) { callbacks.push_back(callback); return size() - 1; }
	void remove_callback(size_t id) { callbacks.erase(std::next(std::begin(callbacks), id)); }
	void clear() { callbacks.clear(); }

	size_t size() const { return callbacks.size(); }

	void fire() { for (auto const &callback : callbacks) callback(); }
	template <typename T, typename ...Args>
	void fire(T const &current, Args const &...rest) { for (auto const &callback : callbacks) callback(current, rest...); }
};

and use it like:

C++:
#include <iostream>
#include <functional>
#include "event_handler.h"

void f1(int i) { std::cout << "f1: " << i << '\n'; }
void f2(int i1, int i2) { std::cout << "f2: " << i1 << ' ' << i2 << '\n'; }

int main()
{
	using callback_type = std::function<void(int)>;
	event_handler<callback_type> e;
	size_t id1 = e.add_callback(f1);
	e.add_callback(f1);
	e.add_callback([](int i) { return f2(i, 2); });
	e.remove_callback(id1);
	e.fire(1);
}

So thats 18 lines of code (this) vs 280 lines of code (yours). And it is completly exception safe, 100% standard conformant (and with that portable), much easier to use (the user doesn't have to mess around with raw pointers returned by your reg method), much more flexible (the user doesn't have to use std::function, only if he needs arbitrary callable types), and seperates implementation details like list, binding or memory management.

Furthermore it uses the STL container std::vector by default, which is much better suited for the means of an event handling system. For simple callback functions, a std::vector will be way faster than a list due to cache locality. Don't trust the O() notation blindly, it often has little value (especially for lists as cache misses aren' modeled for it), look at benchmarks instead. Look especially at the section "Random Insert" and "Conclusion".

If the user has really huge callbacks (which is very unlikely for event callbacks) so that a different container might be suitable, he can just change the underlaying container by passing it as an extra template argument:

C++:
event_handler<callback_type> e1; // Uses default std::vector
event_handler<callback_type, std::list<callback_type>> e2; // Uses std::list

This is much more flexible and abstracts away the concrete container (another reason why iterators should be used).
 
Last edited:
Level 23
Joined
Apr 16, 2012
Messages
4,041
taking double by const ref is waste of dev time, considering that double is 8 bytes and on x64 pointer is also 8 bytes so you save no time whatsoever, + you have extra level of indirection because dereferencing pointer requires access to memory(unless it is stored in cache) whereas by-value double can be kept in registers most of the time.

As LFH said(I read it to see if he pointed out this specifically :D) http://en.cppreference.com/w/cpp/container/list/erase is constant complexity, because iterator for list is most likely just a proxy for given Node, so you already have all you need to do for constant removal.

@LFH: Actually you dont need to do auto const& a : ..., you can just type auto& a : ... and it will correctly expand to const if you try to iterate over const list, because auto type deduction is powered by template type deduction, and they do the same thing
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
@LFH: Actually you dont need to do auto const& a : ..., you can just type auto& a : ... and it will correctly expand to const if you try to iterate over const list, because auto type deduction is powered by template type deduction, and they do the same thing

Yes, you are right, that could be omitted here, was just a quick example ;)
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
Yes, it should just give a short overview about the general performance differences of those containers.

Because linked lists are in most cases just awefully slow. In theory they look nice with O(1) insertion/removal but in practise the cache locality of containers like std::vector outperforms linked lists by far.


@Nes: You should really learn to use containers. This isn't vJass were you re-implement the same list in every single resource you make again and again.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
Your removal is O(n)...

Would have to get the iterator pointing to back or front in O(1).

Your reverse would fail. Add to front or back.

Need to bind on your own for ease of use, similr to thread, so ur API is worse.

Remove is epic fail because it needs to set a bool destroy flag to true. While firing, if destroy is true, erase it, otherwise fire it. Your fire is also crap. It'll break. My current fire (not in post) runs in n, not 2n, plus it doesn't break.

Yea, can def do an STL list, but i'd still need my own node that stores an iterator, so we get what i had before minus insertion and deletion code and next, prev, first, last.

You come in saying what I wrote is garbage and them show something that doesn't even have the same functionality and has worse performance. I fail to comprehend...

I also know how to use containers. The first version did use anln STL list, until i realized that this would be better. STL list is still doubly linked, not a vector, so double allocation. This is single.

Also, STL queue and stack are garbage. I will never use those.

I'd use vector, but i hate reallocating and copying full arrays, and i'm constantly adding/removing events. I should have 2 versions i guess, one for rare remove, used for system registration and stuff, and another for frequent remove.

edit
nvm, urs uses vector, lol. Above was for list. Vector has a slew of other problems. If you add, u can break fire fue to allocation. If you remove, fire can skip an element and can posdibly throw an exception. Removal is always O(n) on this. Can make it O(1), but would destroy order. Due to reallocation, i don't think that it's possible to use a vector for this. It's like you wrote that snippet without thinking. It has to be a list.
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
How does his fire method do O(2n)

what you are doing is trading readability and technically maintainability for that 1% speedup, for real, how much difference does removal of one event_callback from vector of 50 event_callbacks compared to removing them from doubly linked list. The fact that constant insert and O(1) random access totally outweigths the time lost on the removal.

I will say it straight, stop thinking of big O notation as some magical constant, because as proven by previous statement, you can still have faster container, even if removal is slower, because random access and insertation is O(1)(with vector with push_back).

How exactly is his API worse? He at least doesnt return raw pointers, which user ahs no idea if they will be cleaned automatically or not

Noone talked about STL stack.

Yes std::list is doubly linked list, but at that point you are uselessly reinventing the wheel by writing something that is most likely even less optimized than the version shipped with your compiler.

I just want to add that your easy_bind function is totally useless, as it is nonperfect forwarder to std::bind AND requires explicit template type naming

And while Im in here, node->next = NULL; God no....

Exec is horrible name btw.

Last note: with LFH's implementation, I could with a very little bit of work do O(1) removal if I knew the index at which the callback to be removed sits at(if I dont care about order)
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
Ok, this is getting boring.

Your removal is O(n)...

Who cares, for std::vector its constant and a list will never outperform std::vector in a realistic scenario. Also its easy to fix that, the code was just meant to give you an idea how short and compact you can make such a system.


Your reverse would fail. Add to front or back.

What?

Need to bind on your own for ease of use, similr to thread, so ur API is worse.

No, for several reasons:

  • Binding in C++ is done with lambdas or std::bind. Thats the standard way and there is no reason to make a difference for this lib
  • Your reg method is not exception safe.
  • You need the raw pointer returned by reg to remove a specific event. If you don't have that pointer anymore, you can't remove the specific event anymore. So the user has to keep the books of those pointers, otherwise he is lost.
  • Your binding is much worse because it doesn't allow binding of arbitrary functions.

Especially the last point is critical, whats with a function like void foo(int i1, std::string s, int i2) {}? You can't bind it with your method. And thats one of the reasons why binding is kept on user side, because you as a library maker just can't know how the user wants his argument to be bound. In my version he can just write:

C++:
e1.add_callback([](int i1, int i2) { return foo(i1, "hello", i2); })

Short, easy to understand, and as flexible as possible. What if the user needs to bind an external function (from another library) that doesn't match your binding process? He is lost.

Remove is epic fail because it needs to set a bool destroy flag to true. While firing, if destroy is true, erase it, otherwise fire it. Your fire is also crap. It'll break. My current fire (not in post) runs in n, not 2n, plus it doesn't break.

You don't make sense at all.

So if you have a so much better version that does everything better, why don't you post that one? We can only judge what you posted here and that has the same functionality (actually less) than my posted version (I didn't include a fire_reverse, but thats trivial to include), so whats your problem?

And you need O(n) to run n events? Congratulations.

You come in saying what I wrote is garbage and them show something that doesn't even have the same functionality and has worse performance. I fail to comprehend...

Worse performance? Definitly not, prove it, have fun.

I'd use vector, but i hate reallocating and copying full arrays, and i'm constantly adding/removing events. I should have 2 versions i guess, one for rare remove, used for system registration and stuff, and another for frequent remove.

Its not relevant whether you hate copying full arrays or not - if its faster to copy a full array than using a list, go for it. Thats why arbitrary containers should be allow, just like in the version I posted.

And if you really have a performance problem because you have a double linked list instead of a single linked list (which you of course don't have), then write your own single linked list container. Or much better, use boost::intrusive::slist.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
Does a vector iterator contain an index or a pointer? If it's a pointer, the following will throw an exception with your current code.

Given
5 callbacks
vector capacity == vector size

Fire vector
On first callback, add new callback
vector array reallocated, pointers invalid
on next callback, crash, data is gone


Next
on fire
remove current callback (1 only)
All elements shifted down in n
go to next callback (#3)
#2 was skipped


You can't even identify the myriad of bugs in your own code...


Fix one: get rid of STL iteration and just iterate by indices if it is the case that iterator is by pointer
Fix two: store current callback, if dif, call again beore moving on, only if not null**. If null, terminate.

As for std::bind, i matched the API of threads, which is compliant with syandard c++11. You didn't. You did your own thing instead and call me out for following thread API, lol.

So i guess it is indeed possible to use vector if you make those fixes. It'd be a double nested loop. Could also directly erase things immedoately, unlike my newer event. Removal will still be O(n) though, and it will be impossible to return pointers for removal. In fact, it won't just be O(n), it'll be n. You will always have to find the element and then remove it. Plus you'd still need to store pointer to bound function... So double the bad for removal.

Also, no reason you can't bind an arbitrary func and then pass it in :p, so ur point on binding arbitrary functions is mute. However, i shouldn't bind it if there are no params, so i need a specialized method.

I may move to vector since execution speed is more important than removal, but running in n, not O(n), is a pretty big hit, esp if i'm doing A LOT of adding and removing, which i am doing. I do even more executes though, so we'll see.


At edo, O(2n) makes no sense. 2n = O(n).
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
Trivial to fix. Instead of doing for(auto& a : vec) { ... } l you do for(int i = 0; i < vec.size(); ++i)

And before you even point out, you can actually prevent bad stuff happening if you remove callback from list inside the same loop

Also maybe try reading my signature
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
You can't even identify the myriad of bugs in your own code...

For the given example (which is qualitative equal to yours) it works perfectly fine.

Why would I want to add/remove callbacks in a running callback? You neither specified that nor did you include it into your example code. In your example its not even possible because the callbacks don't have access to the event handler.

Also its really trivial to fix that.

As for std::bind, i matched the API of threads, which is compliant with syandard c++11. You didn't. You did your own thing instead and call me out for following thread API, lol.

Where exactly does std::thread class take template parameters? std::thread isn't even a class template, it just has a variadic templated constructor lol. You have a crude mixture here.

At edo, O(2n) makes no sense. 2n = O(n).

Your "running in n, not O(n)" makes no sense either.

... performance ...

You know, the whole discussion here whether a list (single or double) is better than a vector (or something else) is completly pointless because the underlaying container should be configurable via a template argument as already said.

Forcing a user to use a specific container is not acceptable in C++, neither are two different versions (one for std::vector, one for std::list). What if someone wants/needs to use his own container? The container has to be configurable. If he really want to use a singly linked list, he can just pass boost::intrusive::slist as a template parameter.


The real question is: You even say yourself that you are new to C++11. So why don't you want to accept constructive critique from people who are working and using it since a long time?

If you don't want feedback, why posting this in the first place? Regardless of functionality, the coding style and technique is just bad, get over it. C++ uses containers, so use containers. One doesn't use raw pointers with manual memory management in C++, the code is still not exception safe and not even standard conformant, so what do you expect to hear?
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
Your "running in n, not O(n)" makes no sense either.

Actually, that does make sense... but ok

If you run in n, you run in O(n). If you run in O(n), you don't necessarily run in n.

The real question is: You even say yourself that you are new to C++11. So why don't you want to accept constructive critique from people who are working and using it since a long time

This entire debate is pointless. I only wanted comments on the API. This isn't meant to be published good code, so I don't even know why you are commenting on the code, lol...

I like the API, I wanted to share the API. Maybe I should have only posted prototypes. Whatever. My fault for not stating that I was only sharing the API. I haven't slept in like a week, I'm really tired, and I have a million other things I have to code and 0 time to code them, so yea.
 
Last edited:
Level 14
Joined
Dec 12, 2012
Messages
1,007
Actually, that does make sense... but ok

If you run in n, you run in O(n). If you run in O(n), you don't necessarily run in n.

"Running in n" has mathematically no meaning. If you have a list of size 5, you say you run in 5. So 5 what? Seconds? Milliseconds?

Actually Edos O(2n) was already correct, the constant factor is just omitted usually because it is often dependent on implementation details which aren't relevant for a theoretical analysis.

This entire debate is pointless. I only wanted comments on the API. This isn't meant to be published good code, so I don't even know why you are commenting on the code, lol...

Ok, although many of the comments were also API related (raw pointers, naming, binding, etc.). But whatever, I'm out now.
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
Lets face it nes, you cant accept any kind of criticizm from anyone, because you have such high ego you think you are godlike entity that knows the best all the time.

Just for comparision, with LFH's implementation(I had to change fire(...) to perfectly forward arguments) it took 11 milliseconds to fire 10 functions, all pushing incrementing number to shared vector(argument, not global) with reploaded size 100,000 times.

Test code:


C++:
#include <vector>

template <typename callback_type, typename container_type = std::vector<callback_type>>
class EventHandler
{
	container_type callbacks;

public:
	EventHandler() {}
	size_t add_callback(callback_type callback) { callbacks.push_back(callback); return size() - 1; }
	void remove_callback(size_t id) { callbacks.erase(std::next(std::begin(callbacks), id)); }
	void clear() { callbacks.clear(); }

	size_t size() const { return callbacks.size(); }

	void fire() { for (auto const &callback : callbacks) callback(); }
	template <typename T, typename ...Args>
	void fire(T&& current, Args&&... rest)
	{
		for(auto const &callback : callbacks)
			callback(
			std::forward<T>(current),
			std::forward<Args>(rest)...);
	}
};

#include <functional>

void myF1(std::vector<int>& v, int, int)
{
	v.push_back(v.back() + 1);
}

void myF2(std::vector<int>& v, int, int)
{
	v.push_back(v.back() + 1);
}

void myF3(std::vector<int>& v, int, int)
{
	v.push_back(v.back() + 1);
}

void myF4(std::vector<int>& v, int, int)
{
	v.push_back(v.back() + 1);
}

void myF5(std::vector<int>& v, int, int)
{
	v.push_back(v.back() + 1);
}

void myF6(std::vector<int>& v, int, int)
{
	v.push_back(v.back() + 1);
}

void myF7(std::vector<int>& v, int, int)
{
	v.push_back(v.back() + 1);
}

void myF8(std::vector<int>& v, int, int)
{
	v.push_back(v.back() + 1);
}

void myF9(std::vector<int>& v, int, int)
{
	v.push_back(v.back() + 1);
}

void myF10(std::vector<int>& v, int, int)
{
	v.push_back(v.back()+1);
}


#include <ctime>
#include <iostream>
#include <chrono>
#include <random>

class Bench{
	long long c;
public:
	void start()			{ c = __rdtsc();		}
	long long get() const	{ return __rdtsc() - c;	}
	void restart()			{ c = __rdtsc();		}
};

int main()
{
	EventHandler<std::function<void(std::vector<int>&, int, int)>> h;

	h.add_callback(myF1);
	h.add_callback(myF2);
	h.add_callback(myF3);
	h.add_callback(myF4);
	h.add_callback(myF5);
	h.add_callback(myF6);
	h.add_callback(myF7);
	h.add_callback(myF8);
	h.add_callback(myF9);
	h.add_callback(myF10);

	std::vector<int> v;
	v.reserve(10000000);

	std::vector<int> rNums;
	rNums.reserve(v.capacity());

	std::mt19937 mTwist(std::chrono::high_resolution_clock::now().time_since_epoch().count());
	std::uniform_int_distribution<> uDist;

	for(int i = 0; i < v.capacity(); ++i)
	{
		rNums.push_back(uDist(mTwist));
	}

	auto b = Bench{};

	b.start();

	for(int i = 0; i < 100000; ++i)
	{
		h.fire(v, rNums[i], rNums[i]);
	}

	std::cout << "calling 100,000 fires took " << b.get() / 2200000.f << " milliseconds\n";

	std::cout << "size: " << v.size() << "\n";

	std::cin.get();
}


Also nestharus, your library currently uncompilable, because .exec requires rvalues, and I dont know if you know, but lvalue references cant be bound to rvalue references(GG SON).

I cant even test yours, because it is not compiling at all.(function<void(Params...)> actions[length]; is not standard compilant, and I suspect you compile on g++, which means you get this as "Compiler feature" borrowed from C98 standard).

If you can make changes for it to compile, I will happily test your library and see how it did against LFH's.

Before you jump on my way of counting time, yes __rdtsc can be veeery inaccurate, because CPU will change its clock dynamically, but the error is on both tests roughly the same, so for comparision this is pretty good.

EDIT:

Ok, so I made your thing work, and I even wrapped the function array in raii structure, so when calling function throws, or your function throws, you dont leak memory. I also fixed the rvalue reference problem.

It took around 83 milliseconds to execute 100,000 of the same function list, which is around 6x slower.

Here is code sample:


C++:
#include <functional>
#include <type_traits>
#include <utility>
#include <memory>

template <std::size_t... Is>
struct indices {};

template <std::size_t N, std::size_t... Is>
struct build_indices
	: build_indices<N - 1, N - 1, Is...> {};

template <std::size_t... Is>
struct build_indices<0, Is...> : indices<Is...>{};

template<int I> struct placeholder{};

namespace std{
	template<int I>
	struct is_placeholder< ::placeholder<I>> : std::integral_constant<int, I>{};
} // std::

namespace detail{
	template<std::size_t... Is, class F, class... Args>
	auto easy_bind(indices<Is...>, F const& f, Args&&... args)
		-> decltype(std::bind(f, std::forward<Args>(args)..., placeholder<1 + Is>{}...))
	{
		return std::bind(f, std::forward<Args>(args)..., placeholder<1 + Is>{}...);
	}
} // detail::

template<class F, class... FArgs, class... Args>
auto easy_bind(F const& f, Args&&... args)
-> decltype(detail::easy_bind(build_indices<sizeof...(FArgs)-sizeof...(Args)>{}, f, std::forward<Args>(args)...))
{
	return detail::easy_bind(build_indices<sizeof...(FArgs)-sizeof...(Args)>{}, f, std::forward<Args>(args)...);
}

/*
*    Event
*
*       Event()
*       ~Event()
*
*       void clear()
*       void reverse()
*
*       Event::id reg(function, args...)

*
*       void unreg(Event::id eventId)
*           -   removes function on id in O(1)
*
*       void exec()
*           -   runs all functions on Event
*/

using namespace std;

template<typename... Params>
class Event
{
	/*
	*    Structures
	*/
	struct Node
	{
		function<void(Params...)> dat;
		Node* next;
		Node* prev;
	}; //Node

	/*
	*   Fields
	*/
	Node* first;
	Node* last;

	bool reversed;
	int length;

public:
	/*
	*   Types
	*/
	typedef Node* id;

	/*
	*   Constructors
	*/
	Event()
	{
		length = 0;

		first = NULL;
		last = NULL;

		reversed = false;
	} //Event

	~Event()
	{
		clear();
	} //~Event

	/*
	*   Data Manipulation Operations
	*/
	template<class T, class... Args>
	id reg(T&& func, Args&&... args)
	{
		++length;

		Node* node = new Node();
		node->dat = easy_bind<T, Params..., Args...>(func, args...);

		if(reversed)
		{
			node->prev = NULL;
			node->next = first;

			if(first == NULL)
			{
				last = node;
			} //if
			else
			{
				first->prev = node;
			} //else

			first = node;
		} //if
		else
		{
			node->next = NULL;
			node->prev = last;

			if(last == NULL)
			{
				first = node;
			} //if
			else
			{
				last->next = node;
			} //else

			last = node;
		} //else

		return node;
	} //reg

	void unreg(id eventId)
	{
		--length;

		if(eventId->next)
		{
			eventId->next->prev = eventId->prev;
		} //if
		else
		{
			last = eventId->prev;
		} //else
		if(eventId->prev)
		{
			eventId->prev->next = eventId->next;
		}
		else
		{
			first = eventId->next;
		} //else

		delete eventId;
	} //unreg

	void clear()
	{
		Node* next;

		while(first)
		{
			next = first->next;
			delete first;
			first = next;
		} //while

		last = NULL;
	} //clear

	/*
	*   Structure Manipulation Operations
	*
	*/
	void reverse()
	{
		if(first)
		{
			Node* node = first;
			Node* next = node->next;

			while(next)
			{
				node->next = node->prev;
				node->prev = next;
				node = next;
				next = node->next;
			} //while

			last->next = last->prev;
			last->prev = next;

			node = last;
			last = first;
			first = node;

			reversed = !reversed;
		} //if
	} //reverse

	/*
	*   Execution Operations
	*/
	void exec(Params&... args)
	{
		if(first)
		{
			struct _wrapper{
				std::unique_ptr<function<void(Params...)>[]> raii;
				_wrapper(std::size_t size) : raii(new function<void(Params...)>[size])
				{}
			};

			_wrapper w(length);

			auto actions = w.raii.get();

			int i = 0;

			Node* node = first;

			while(node)
			{
				actions[i++] = node->dat;
				node = node->next;
			} //while

			int length = this->length;

			for(i = 0; i < length; ++i)
			{
				actions[i](args...);
			} //for
		} //if
	} //exec
}; //Event


#include <functional>
#include <vector>

void myF1(std::vector<int>& v, int, int)
{
	v.push_back(v.back() + 1);
}

void myF2(std::vector<int>& v, int, int)
{
	v.push_back(v.back() + 1);
}

void myF3(std::vector<int>& v, int, int)
{
	v.push_back(v.back() + 1);
}

void myF4(std::vector<int>& v, int, int)
{
	v.push_back(v.back() + 1);
}

void myF5(std::vector<int>& v, int, int)
{
	v.push_back(v.back() + 1);
}

void myF6(std::vector<int>& v, int, int)
{
	v.push_back(v.back() + 1);
}

void myF7(std::vector<int>& v, int, int)
{
	v.push_back(v.back() + 1);
}

void myF8(std::vector<int>& v, int, int)
{
	v.push_back(v.back() + 1);
}

void myF9(std::vector<int>& v, int, int)
{
	v.push_back(v.back() + 1);
}

void myF10(std::vector<int>& v, int, int)
{
	v.push_back(v.back() + 1);
}


#include <ctime>
#include <iostream>
#include <chrono>
#include <random>

class Bench{
	long long c;
public:
	void start()			{ c = __rdtsc(); }
	long long get() const	{ return __rdtsc() - c; }
	void restart()			{ c = __rdtsc(); }
};

int main()
{
	Event<std::vector<int>&, int, int> ev;

	auto ev1P = ev.reg(myF1);
	auto ev2P = ev.reg(myF2);
	auto ev3P = ev.reg(myF3);
	auto ev4P = ev.reg(myF4);
	auto ev5P = ev.reg(myF5);
	auto ev6P = ev.reg(myF6);
	auto ev7P = ev.reg(myF7);
	auto ev8P = ev.reg(myF8);
	auto ev9P = ev.reg(myF9);
	auto ev10P = ev.reg(myF10);

	std::vector<int> v;
	v.reserve(10000000);

	std::vector<int> rNums;
	rNums.reserve(v.capacity());

	std::mt19937 mTwist(std::chrono::high_resolution_clock::now().time_since_epoch().count());
	std::uniform_int_distribution<> uDist;

	for(int i = 0; i < v.capacity(); ++i)
	{
		rNums.push_back(uDist(mTwist));
	}

	auto b = Bench{};

	b.start();

	for(int i = 0; i < 100000; ++i)
	{
		ev.exec(v, rNums[i], rNums[i]);
	}

	std::cout << "calling 100,000 fires took " << b.get() / 2200000.f << " milliseconds\n";

	std::cout << "size: " << v.size() << "\n";

	std::cin.get();
}
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
sigh, if I wanted it benchmarked, i woulda uploaded the later version =p, lol

whatever

you say my API sucks, but I think this

EventHandler<std::function<void(std::vector<int>&, int, int)>>

is a lot uglier than this

Event<std::vector<int>&, int int>

=p

Oh well, that's just me

reg and exec are terrible method names, I agree. However, the params are cool =P

I guess I can bench it later just out of curiosity with the correct code, since 83 ms is wrong ; )


bleh, here is code I'm currently using. Still not the best, but whatever. It won't win on execution speed since it uses a list and a vector, but it has faster removal speed.

It's not perfect since the fixes are just temporary as I don't have time >.>

some things about the API are good, many things are bad. As I mentioned, it wasn't like I submitted this to be published, lol. Just thought the API with the nested templates was pretty cool, hence the name of the thread "fun with variadic templates." But go ahead and criticize everything else, whatever. Don't care =).

Code:
#pragma once

/*
*	Event
*
*		Event()
*		~Event()
*		//	don't destroy an event while executing it
*
*		void clear()
*		void reverse()
*
*		Event::id reg(function, args...)

*		Event::id->enable = true|false;
*		Event::id->unreg();
*		Event::id->replace(function, args...)
*
*		void exec()
*			-	runs all functions on Event
*/

#include "EasyBind.h"

template<typename... Params>
class Event
{
    struct Node;
    
public:
    /*
	*	Types
	*/
	typedef Node* id;
    
private:
    
    /*
    *    Structures
    */
    struct Node
    {
        std::function<void(Params...)> dat;
        bool enable;
        bool destroy;
		Node* next;
		Node* prev;
		Event* event;

		inline void unreg()
		{
			destroy = true;
		} //unreg

		template<class T, class... Args>
		void replace(T&& func, Args&&... args)
        {
            dat = easy_bindEx<T, Params..., Args...>(func, args...);
        } //replace
	}; //Node

	/*
	*	Fields
	*/
	Node* first;
	Node* last;

	bool reversed;

	void unreg(id eventId)
	{
		if (eventId->next)
		{
			eventId->next->prev = eventId->prev;
		} //if
		else
		{
			last = eventId->prev;
		} //else
		if (eventId->prev)
		{
			eventId->prev->next = eventId->next;
		}
		else
		{
			first = eventId->next;
		} //else
	} //unreg

public:
	/*
	*	Constructors
	*/
	Event()
	{
		first = NULL;
		last = NULL;

		reversed = false;
	} //Event

	~Event()
	{
		Node* next;

		while (first)
		{
			next = first->next;
			delete first;
			first = next;
		} //while

		last = NULL;
	} //~Event

	/*
	*	Data Manipulation Operations
	*/
	template<class T, class... Args>
	id reg(T&& func, Args&&... args)
	{
		Node* node = new Node();
		node->dat = easy_bindEx<T, Params..., Args...>(func, args...);
		node->event = this;
		node->enable = true;
		node->destroy = false;

		if (reversed)
		{
			node->prev = NULL;
			node->next = first;

			if (first == NULL)
			{
				last = node;
			} //if
			else
			{
				first->prev = node;
			} //else

			first = node;
		} //if
		else
		{
			node->next = NULL;
			node->prev = last;

			if (last == NULL)
			{
				first = node;
			} //if
			else
			{
				last->next = node;
			} //else

			last = node;
		} //else

		return node;
	} //reg

	/*
    template<class... P, class... Args>
    id regEvent(Event<P...>& event, Args&&... args)
    {
        easy_bind(std::function<void(Event<P...>&, P...)>([](Event<P...>& event, Args&&... args, Params&&... params) { event.exec(forward<Args>(args)..., forward<Params>(params)...);}), event, args...);
        return NULL;
        //return reg(easy_bind(std::function<void(Event<P...>&, P...)>([](Event<P...>& event, Args&&... args, Params&&... params) { event.exec(forward<Args>(args)..., forward<Params>(params)...);}), event, args...));
    } //regEvent
    */

	void clear()
	{
		for (Node* node = first; node; node = node->next)
		{
			node->unreg();
		} //for
	} //clear

	/*
	*	Structure Manipulation Operations
	*
	*/
	void reverse()
	{
		if (first)
		{
			Node* node = first;
			Node* next = node->next;

			while (next)
			{
				node->next = node->prev;
				node->prev = next;
				node = next;
				next = node->next;
			} //while

			last->next = last->prev;
			last->prev = next;

			node = last;
			last = first;
			first = node;

			reversed = !reversed;
		} //if
	} //reverse
	
	/*
	*	Execution Operations
	*/
	void exec(Params&&... args)
	{
		Node* node = first;
		Node* next;

		while (node)
		{
			next = node->next;

			if (node->destroy)
			{
				unreg(node);
				delete node;
			} //if
			else if (node->enable)
			{
				node->dat(args...);
			} //else if

			node = next;
		} //while
	} //exec
}; //Event

template<>
class Event<void>
{
    struct Node;
    
public:
    /*
	*	Types
	*/
	typedef Node* id;
    
private:
    
    /*
    *    Structures
    */
    struct Node
    {
        std::function<void()> dat;
        bool enable;
        bool destroy;
		Node* next;
		Node* prev;
		Event* event;

		inline void unreg()
		{
			destroy = true;
		} //unreg

		template<class T, class... Args>
		void replace(T&& func, Args&&... args)
        {
            dat = easy_bindEx<T, Args...>(func, args...);
        } //replace
	}; //Node

	/*
	*	Fields
	*/
	Node* first;
	Node* last;

	bool reversed;

	void unreg(id eventId)
	{
		if (eventId->next)
		{
			eventId->next->prev = eventId->prev;
		} //if
		else
		{
			last = eventId->prev;
		} //else
		if (eventId->prev)
		{
			eventId->prev->next = eventId->next;
		}
		else
		{
			first = eventId->next;
		} //else
	} //unreg

public:
	/*
	*	Constructors
	*/
	Event()
	{
		first = NULL;
		last = NULL;

		reversed = false;
	} //Event

	~Event()
	{
		Node* next;

		while (first)
		{
			next = first->next;
			delete first;
			first = next;
		} //while

		last = NULL;
	} //~Event

	/*
	*	Data Manipulation Operations
	*/
	template<class T, class... Args>
	id reg(T&& func, Args&&... args)
	{
		Node* node = new Node();
		node->dat = easy_bindEx<T, Args...>(func, args...);
		node->event = this;
		node->enable = true;
		node->destroy = false;

		if (reversed)
		{
			node->prev = NULL;
			node->next = first;

			if (first == NULL)
			{
				last = node;
			} //if
			else
			{
				first->prev = node;
			} //else

			first = node;
		} //if
		else
		{
			node->next = NULL;
			node->prev = last;

			if (last == NULL)
			{
				first = node;
			} //if
			else
			{
				last->next = node;
			} //else

			last = node;
		} //else

		return node;
	} //reg

	void clear()
	{
		for (Node* node = first; node; node = node->next)
		{
			node->unreg();
		} //for
	} //clear

	/*
	*	Structure Manipulation Operations
	*
	*/
	void reverse()
	{
		if (first)
		{
			Node* node = first;
			Node* next = node->next;

			while (next)
			{
				node->next = node->prev;
				node->prev = next;
				node = next;
				next = node->next;
			} //while

			last->next = last->prev;
			last->prev = next;

			node = last;
			last = first;
			first = node;

			reversed = !reversed;
		} //if
	} //reverse
	
	/*
	*	Execution Operations
	*/
	void exec()
	{
		Node* node = first;
		Node* next;

		while (node)
		{
			next = node->next;

			if (node->destroy)
			{
				unreg(node);
				delete node;
			} //if
			else if (node->enable)
			{
				node->dat();
			} //else if

			node = next;
		} //while
	} //exec
}; //Event
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
I benched what you wrote in first post.

I cant retest it right now on my compiler, because on this computer I only have visual 2012, which doesnt support variadic templates(I am aware of it being able to be turned on).

By the way, your original code still doesnt compile if you want to pass anything but temporaries into exec (http://ideone.com/7Ec215)

EDIT:

I noticed the change in .exec method, which now takes nothing, which makes your library even harder to use, because now I cant even actually test it, because I cant loop over the methods and call it with different arguments, because you bind the arguments to the function call when you register it.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
uh... I changed easy_bind, lulz...

I call it with regular lvalues... but ok

Code:
#pragma once

//http://stackoverflow.com/questions/15024223/how-to-implement-an-easy-bind-that-automagically-inserts-implied-placeholders

#include <functional>
#include <type_traits>
#include <utility>

template <std::size_t... Is>
struct indices {};

template <std::size_t N, std::size_t... Is>
struct build_indices
  : build_indices<N-1, N-1, Is...> {};

template <std::size_t... Is>
struct build_indices<0, Is...> : indices<Is...> {};

template<int I> struct placeholder{};

namespace std{
template<int I>
struct is_placeholder< ::placeholder<I>> : std::integral_constant<int, I>{};
} // std::

namespace detail{
template<std::size_t... Is, class F, class... Args>
auto easy_bind(indices<Is...>, F const& f, Args&&... args)
  -> decltype(std::bind(f, std::forward<Args>(args)..., placeholder<1 + Is>{}...))
{
    return std::bind(f, std::forward<Args>(args)..., placeholder<1 + Is>{}...);
}
} // detail::

template<class R, class... FArgs, class... Args>
auto easy_bind(std::function<R(FArgs...)> const& f, Args&&... args)
    -> decltype(detail::easy_bind(build_indices<sizeof...(FArgs) - sizeof...(Args)>{}, f, std::forward<Args>(args)...))
{
    return detail::easy_bind(build_indices<sizeof...(FArgs) - sizeof...(Args)>{}, f, std::forward<Args>(args)...);
}

template<class F, class... FArgs, class... Args>
auto easy_bindEx(F const& f, Args&&... args)
    -> decltype(detail::easy_bind(build_indices<sizeof...(FArgs) - sizeof...(Args)>{}, f, std::forward<Args>(args)...))
{
    return detail::easy_bind(build_indices<sizeof...(FArgs) - sizeof...(Args)>{}, f, std::forward<Args>(args)...);
}


Well, here it is. Doesn't compile since you are doing stuff that I'm not. Don't care, so whatever. This is more of a prototype than anything else. Sooner you understand this, the better ; ).

Code:
#include <functional>
#include <type_traits>
#include <utility>

template <std::size_t... Is>
struct indices {};

template <std::size_t N, std::size_t... Is>
struct build_indices
  : build_indices<N-1, N-1, Is...> {};

template <std::size_t... Is>
struct build_indices<0, Is...> : indices<Is...> {};

template<int I> struct placeholder{};

namespace std{
template<int I>
struct is_placeholder< ::placeholder<I>> : std::integral_constant<int, I>{};
} // std::

namespace detail{
template<std::size_t... Is, class F, class... Args>
auto easy_bind(indices<Is...>, F const& f, Args&&... args)
  -> decltype(std::bind(f, std::forward<Args>(args)..., placeholder<1 + Is>{}...))
{
    return std::bind(f, std::forward<Args>(args)..., placeholder<1 + Is>{}...);
}
} // detail::

template<class R, class... FArgs, class... Args>
auto easy_bind(std::function<R(FArgs...)> const& f, Args&&... args)
    -> decltype(detail::easy_bind(build_indices<sizeof...(FArgs) - sizeof...(Args)>{}, f, std::forward<Args>(args)...))
{
    return detail::easy_bind(build_indices<sizeof...(FArgs) - sizeof...(Args)>{}, f, std::forward<Args>(args)...);
}

template<class F, class... FArgs, class... Args>
auto easy_bindEx(F const& f, Args&&... args)
    -> decltype(detail::easy_bind(build_indices<sizeof...(FArgs) - sizeof...(Args)>{}, f, std::forward<Args>(args)...))
{
    return detail::easy_bind(build_indices<sizeof...(FArgs) - sizeof...(Args)>{}, f, std::forward<Args>(args)...);
}

template<typename... Params>
class Event
{
    struct Node;
    
public:
    /*
    *   Types
    */
    typedef Node* id;
    
private:
    
    /*
    *    Structures
    */
    struct Node
    {
        std::function<void(Params...)> dat;
        bool enable;
        bool destroy;
        Node* next;
        Node* prev;
        Event* event;

        inline void unreg()
        {
            destroy = true;
        } //unreg

        template<class T, class... Args>
        void replace(T&& func, Args&&... args)
        {
            dat = easy_bindEx<T, Params..., Args...>(func, args...);
        } //replace
    }; //Node

    /*
    *   Fields
    */
    Node* first;
    Node* last;

    bool reversed;

    void unreg(id eventId)
    {
        if (eventId->next)
        {
            eventId->next->prev = eventId->prev;
        } //if
        else
        {
            last = eventId->prev;
        } //else
        if (eventId->prev)
        {
            eventId->prev->next = eventId->next;
        }
        else
        {
            first = eventId->next;
        } //else
    } //unreg

public:
    /*
    *   Constructors
    */
    Event()
    {
        first = NULL;
        last = NULL;

        reversed = false;
    } //Event

    ~Event()
    {
        Node* next;

        while (first)
        {
            next = first->next;
            delete first;
            first = next;
        } //while

        last = NULL;
    } //~Event

    /*
    *   Data Manipulation Operations
    */
    template<class T, class... Args>
    id reg(T&& func, Args&&... args)
    {
        Node* node = new Node();
        node->dat = easy_bindEx<T, Params..., Args...>(func, args...);
        node->event = this;
        node->enable = true;
        node->destroy = false;

        if (reversed)
        {
            node->prev = NULL;
            node->next = first;

            if (first == NULL)
            {
                last = node;
            } //if
            else
            {
                first->prev = node;
            } //else

            first = node;
        } //if
        else
        {
            node->next = NULL;
            node->prev = last;

            if (last == NULL)
            {
                first = node;
            } //if
            else
            {
                last->next = node;
            } //else

            last = node;
        } //else

        return node;
    } //reg

    /*
    template<class... P, class... Args>
    id regEvent(Event<P...>& event, Args&&... args)
    {
        easy_bind(std::function<void(Event<P...>&, P...)>([](Event<P...>& event, Args&&... args, Params&&... params) { event.exec(forward<Args>(args)..., forward<Params>(params)...);}), event, args...);
        return NULL;
        //return reg(easy_bind(std::function<void(Event<P...>&, P...)>([](Event<P...>& event, Args&&... args, Params&&... params) { event.exec(forward<Args>(args)..., forward<Params>(params)...);}), event, args...));
    } //regEvent
    */

    void clear()
    {
        for (Node* node = first; node; node = node->next)
        {
            node->unreg();
        } //for
    } //clear

    /*
    *   Structure Manipulation Operations
    *
    */
    void reverse()
    {
        if (first)
        {
            Node* node = first;
            Node* next = node->next;

            while (next)
            {
                node->next = node->prev;
                node->prev = next;
                node = next;
                next = node->next;
            } //while

            last->next = last->prev;
            last->prev = next;

            node = last;
            last = first;
            first = node;

            reversed = !reversed;
        } //if
    } //reverse
    
    /*
    *   Execution Operations
    */
    void exec(Params&&... args)
    {
        Node* node = first;
        Node* next;

        while (node)
        {
            next = node->next;

            if (node->destroy)
            {
                unreg(node);
                delete node;
            } //if
            else if (node->enable)
            {
                node->dat(args...);
            } //else if

            node = next;
        } //while
    } //exec
}; //Event

template<>
class Event<void>
{
    struct Node;
    
public:
    /*
    *   Types
    */
    typedef Node* id;
    
private:
    
    /*
    *    Structures
    */
    struct Node
    {
        std::function<void()> dat;
        bool enable;
        bool destroy;
        Node* next;
        Node* prev;
        Event* event;

        inline void unreg()
        {
            destroy = true;
        } //unreg

        template<class T, class... Args>
        void replace(T&& func, Args&&... args)
        {
            dat = easy_bindEx<T, Args...>(func, args...);
        } //replace
    }; //Node

    /*
    *   Fields
    */
    Node* first;
    Node* last;

    bool reversed;

    void unreg(id eventId)
    {
        if (eventId->next)
        {
            eventId->next->prev = eventId->prev;
        } //if
        else
        {
            last = eventId->prev;
        } //else
        if (eventId->prev)
        {
            eventId->prev->next = eventId->next;
        }
        else
        {
            first = eventId->next;
        } //else
    } //unreg

public:
    /*
    *   Constructors
    */
    Event()
    {
        first = NULL;
        last = NULL;

        reversed = false;
    } //Event

    ~Event()
    {
        Node* next;

        while (first)
        {
            next = first->next;
            delete first;
            first = next;
        } //while

        last = NULL;
    } //~Event

    /*
    *   Data Manipulation Operations
    */
    template<class T, class... Args>
    id reg(T&& func, Args&&... args)
    {
        Node* node = new Node();
        node->dat = easy_bindEx<T, Args...>(func, args...);
        node->event = this;
        node->enable = true;
        node->destroy = false;

        if (reversed)
        {
            node->prev = NULL;
            node->next = first;

            if (first == NULL)
            {
                last = node;
            } //if
            else
            {
                first->prev = node;
            } //else

            first = node;
        } //if
        else
        {
            node->next = NULL;
            node->prev = last;

            if (last == NULL)
            {
                first = node;
            } //if
            else
            {
                last->next = node;
            } //else

            last = node;
        } //else

        return node;
    } //reg

    void clear()
    {
        for (Node* node = first; node; node = node->next)
        {
            node->unreg();
        } //for
    } //clear

    /*
    *   Structure Manipulation Operations
    *
    */
    void reverse()
    {
        if (first)
        {
            Node* node = first;
            Node* next = node->next;

            while (next)
            {
                node->next = node->prev;
                node->prev = next;
                node = next;
                next = node->next;
            } //while

            last->next = last->prev;
            last->prev = next;

            node = last;
            last = first;
            first = node;

            reversed = !reversed;
        } //if
    } //reverse
    
    /*
    *   Execution Operations
    */
    void exec()
    {
        Node* node = first;
        Node* next;

        while (node)
        {
            next = node->next;

            if (node->destroy)
            {
                unreg(node);
                delete node;
            } //if
            else if (node->enable)
            {
                node->dat();
            } //else if

            node = next;
        } //while
    } //exec
}; //Event

#include <functional>
#include <vector>

void myF1(std::vector<int>& v, int, int)
{
    v.push_back(v.back() + 1);
}

void myF2(std::vector<int>& v, int, int)
{
    v.push_back(v.back() + 1);
}

void myF3(std::vector<int>& v, int, int)
{
    v.push_back(v.back() + 1);
}

void myF4(std::vector<int>& v, int, int)
{
    v.push_back(v.back() + 1);
}

void myF5(std::vector<int>& v, int, int)
{
    v.push_back(v.back() + 1);
}

void myF6(std::vector<int>& v, int, int)
{
    v.push_back(v.back() + 1);
}

void myF7(std::vector<int>& v, int, int)
{
    v.push_back(v.back() + 1);
}

void myF8(std::vector<int>& v, int, int)
{
    v.push_back(v.back() + 1);
}

void myF9(std::vector<int>& v, int, int)
{
    v.push_back(v.back() + 1);
}

void myF10(std::vector<int>& v, int, int)
{
    v.push_back(v.back() + 1);
}


#include <ctime>
#include <iostream>
#include <chrono>
#include <random>


int main()
{
    Event<std::vector<int>&, int, int> ev;

    ev.reg(myF1);
    ev.reg(myF2);
    ev.reg(myF3);
    ev.reg(myF4);
    ev.reg(myF5);
    ev.reg(myF6);
    ev.reg(myF7);
    ev.reg(myF8);
    ev.reg(myF9);
    ev.reg(myF10);

    std::vector<int> v;
    v.reserve(10000000);

    std::vector<int> rNums;
    rNums.reserve(v.capacity());

    std::mt19937 mTwist(std::chrono::high_resolution_clock::now().time_since_epoch().count());
    std::uniform_int_distribution<> uDist;

    for(unsigned int i = 0; i < v.capacity(); ++i)
    {
        rNums.push_back(uDist(mTwist));
    }

	auto now = std::chrono::high_resolution_clock::now();

    for(int i = 0; i < 100000; ++i)
    {
        ev.exec(v, rNums[i], rNums[i]);
    }
	
	auto after = std::chrono::high_resolution_clock::now();

    std::cout << "calling 100,000 fires took " <<
		std::chrono::duration_cast<std::chrono::microseconds>(after - now).count() << " milliseconds\n";

    std::cout << "size: " << v.size() << "\n";

    std::cin.get();
}



Here is how I typically use it
Code:
#include <functional>
#include <type_traits>
#include <utility>

template <std::size_t... Is>
struct indices {};

template <std::size_t N, std::size_t... Is>
struct build_indices
  : build_indices<N-1, N-1, Is...> {};

template <std::size_t... Is>
struct build_indices<0, Is...> : indices<Is...> {};

template<int I> struct placeholder{};

namespace std{
template<int I>
struct is_placeholder< ::placeholder<I>> : std::integral_constant<int, I>{};
} // std::

namespace detail{
template<std::size_t... Is, class F, class... Args>
auto easy_bind(indices<Is...>, F const& f, Args&&... args)
  -> decltype(std::bind(f, std::forward<Args>(args)..., placeholder<1 + Is>{}...))
{
    return std::bind(f, std::forward<Args>(args)..., placeholder<1 + Is>{}...);
}
} // detail::

template<class R, class... FArgs, class... Args>
auto easy_bind(std::function<R(FArgs...)> const& f, Args&&... args)
    -> decltype(detail::easy_bind(build_indices<sizeof...(FArgs) - sizeof...(Args)>{}, f, std::forward<Args>(args)...))
{
    return detail::easy_bind(build_indices<sizeof...(FArgs) - sizeof...(Args)>{}, f, std::forward<Args>(args)...);
}

template<class F, class... FArgs, class... Args>
auto easy_bindEx(F const& f, Args&&... args)
    -> decltype(detail::easy_bind(build_indices<sizeof...(FArgs) - sizeof...(Args)>{}, f, std::forward<Args>(args)...))
{
    return detail::easy_bind(build_indices<sizeof...(FArgs) - sizeof...(Args)>{}, f, std::forward<Args>(args)...);
}

template<typename... Params>
class Event
{
    struct Node;
    
public:
    /*
    *   Types
    */
    typedef Node* id;
    
private:
    
    /*
    *    Structures
    */
    struct Node
    {
        std::function<void(Params...)> dat;
        bool enable;
        bool destroy;
        Node* next;
        Node* prev;
        Event* event;

        inline void unreg()
        {
            destroy = true;
        } //unreg

        template<class T, class... Args>
        void replace(T&& func, Args&&... args)
        {
            dat = easy_bindEx<T, Params..., Args...>(func, args...);
        } //replace
    }; //Node

    /*
    *   Fields
    */
    Node* first;
    Node* last;

    bool reversed;

    void unreg(id eventId)
    {
        if (eventId->next)
        {
            eventId->next->prev = eventId->prev;
        } //if
        else
        {
            last = eventId->prev;
        } //else
        if (eventId->prev)
        {
            eventId->prev->next = eventId->next;
        }
        else
        {
            first = eventId->next;
        } //else
    } //unreg

public:
    /*
    *   Constructors
    */
    Event()
    {
        first = NULL;
        last = NULL;

        reversed = false;
    } //Event

    ~Event()
    {
        Node* next;

        while (first)
        {
            next = first->next;
            delete first;
            first = next;
        } //while

        last = NULL;
    } //~Event

    /*
    *   Data Manipulation Operations
    */
    template<class T, class... Args>
    id reg(T&& func, Args&&... args)
    {
        Node* node = new Node();
        node->dat = easy_bindEx<T, Params..., Args...>(func, args...);
        node->event = this;
        node->enable = true;
        node->destroy = false;

        if (reversed)
        {
            node->prev = NULL;
            node->next = first;

            if (first == NULL)
            {
                last = node;
            } //if
            else
            {
                first->prev = node;
            } //else

            first = node;
        } //if
        else
        {
            node->next = NULL;
            node->prev = last;

            if (last == NULL)
            {
                first = node;
            } //if
            else
            {
                last->next = node;
            } //else

            last = node;
        } //else

        return node;
    } //reg

    /*
    template<class... P, class... Args>
    id regEvent(Event<P...>& event, Args&&... args)
    {
        easy_bind(std::function<void(Event<P...>&, P...)>([](Event<P...>& event, Args&&... args, Params&&... params) { event.exec(forward<Args>(args)..., forward<Params>(params)...);}), event, args...);
        return NULL;
        //return reg(easy_bind(std::function<void(Event<P...>&, P...)>([](Event<P...>& event, Args&&... args, Params&&... params) { event.exec(forward<Args>(args)..., forward<Params>(params)...);}), event, args...));
    } //regEvent
    */

    void clear()
    {
        for (Node* node = first; node; node = node->next)
        {
            node->unreg();
        } //for
    } //clear

    /*
    *   Structure Manipulation Operations
    *
    */
    void reverse()
    {
        if (first)
        {
            Node* node = first;
            Node* next = node->next;

            while (next)
            {
                node->next = node->prev;
                node->prev = next;
                node = next;
                next = node->next;
            } //while

            last->next = last->prev;
            last->prev = next;

            node = last;
            last = first;
            first = node;

            reversed = !reversed;
        } //if
    } //reverse
    
    /*
    *   Execution Operations
    */
    void exec(Params&&... args)
    {
        Node* node = first;
        Node* next;

        while (node)
        {
            next = node->next;

            if (node->destroy)
            {
                unreg(node);
                delete node;
            } //if
            else if (node->enable)
            {
                node->dat(args...);
            } //else if

            node = next;
        } //while
    } //exec
}; //Event

template<>
class Event<void>
{
    struct Node;
    
public:
    /*
    *   Types
    */
    typedef Node* id;
    
private:
    
    /*
    *    Structures
    */
    struct Node
    {
        std::function<void()> dat;
        bool enable;
        bool destroy;
        Node* next;
        Node* prev;
        Event* event;

        inline void unreg()
        {
            destroy = true;
        } //unreg

        template<class T, class... Args>
        void replace(T&& func, Args&&... args)
        {
            dat = easy_bindEx<T, Args...>(func, args...);
        } //replace
    }; //Node

    /*
    *   Fields
    */
    Node* first;
    Node* last;

    bool reversed;

    void unreg(id eventId)
    {
        if (eventId->next)
        {
            eventId->next->prev = eventId->prev;
        } //if
        else
        {
            last = eventId->prev;
        } //else
        if (eventId->prev)
        {
            eventId->prev->next = eventId->next;
        }
        else
        {
            first = eventId->next;
        } //else
    } //unreg

public:
    /*
    *   Constructors
    */
    Event()
    {
        first = NULL;
        last = NULL;

        reversed = false;
    } //Event

    ~Event()
    {
        Node* next;

        while (first)
        {
            next = first->next;
            delete first;
            first = next;
        } //while

        last = NULL;
    } //~Event

    /*
    *   Data Manipulation Operations
    */
    template<class T, class... Args>
    id reg(T&& func, Args&&... args)
    {
        Node* node = new Node();
        node->dat = easy_bindEx<T, Args...>(func, args...);
        node->event = this;
        node->enable = true;
        node->destroy = false;

        if (reversed)
        {
            node->prev = NULL;
            node->next = first;

            if (first == NULL)
            {
                last = node;
            } //if
            else
            {
                first->prev = node;
            } //else

            first = node;
        } //if
        else
        {
            node->next = NULL;
            node->prev = last;

            if (last == NULL)
            {
                first = node;
            } //if
            else
            {
                last->next = node;
            } //else

            last = node;
        } //else

        return node;
    } //reg

    void clear()
    {
        for (Node* node = first; node; node = node->next)
        {
            node->unreg();
        } //for
    } //clear

    /*
    *   Structure Manipulation Operations
    *
    */
    void reverse()
    {
        if (first)
        {
            Node* node = first;
            Node* next = node->next;

            while (next)
            {
                node->next = node->prev;
                node->prev = next;
                node = next;
                next = node->next;
            } //while

            last->next = last->prev;
            last->prev = next;

            node = last;
            last = first;
            first = node;

            reversed = !reversed;
        } //if
    } //reverse
    
    /*
    *   Execution Operations
    */
    void exec()
    {
        Node* node = first;
        Node* next;

        while (node)
        {
            next = node->next;

            if (node->destroy)
            {
                unreg(node);
                delete node;
            } //if
            else if (node->enable)
            {
                node->dat();
            } //else if

            node = next;
        } //while
    } //exec
}; //Event

#include <functional>
#include <vector>

void myF1(const int&, const int&)
{
}


#include <ctime>
#include <iostream>
#include <chrono>
#include <random>


int main()
{
    Event<const int&, const int&> ev;

    ev.reg(myF1);
    ev.reg(myF1);
    ev.reg(myF1);
    ev.reg(myF1);
    ev.reg(myF1);
    ev.reg(myF1);
    ev.reg(myF1);
    ev.reg(myF1);
    ev.reg(myF1);
    ev.reg(myF1);

    std::vector<int> v;
    v.reserve(10000000);

    std::vector<int> rNums;
    rNums.reserve(v.capacity());

    std::mt19937 mTwist(std::chrono::high_resolution_clock::now().time_since_epoch().count());
    std::uniform_int_distribution<> uDist;

    for(unsigned int i = 0; i < v.capacity(); ++i)
    {
        rNums.push_back(uDist(mTwist));
    }

	auto now = std::chrono::high_resolution_clock::now();

    for(int i = 0; i < 100000; ++i)
    {
        ev.exec(1, 2);
    }
	
	auto after = std::chrono::high_resolution_clock::now();

    std::cout << "calling 100,000 fires took " <<
		std::chrono::duration_cast<std::chrono::microseconds>(after - now).count() << " milliseconds\n";

    std::cout << "size: " << v.size() << "\n";

    std::cin.get();
}

Here is result

calling 100,000 fires took 5934 milliseconds


This is to be expected


About the only thing I ever pass into this event is a long unsigned double. Nothing else ever gets passed because all event responses can be retrieved from the sources directly =). The long double is from a lower layer.
 
Status
Not open for further replies.
Top