April 26, 2016

std::function as delegate

After my previous post some people asked why did I invent what was invented already? Why did I try to write another std::function? Well, from the begining I planed to compare custom and standard approach. So here we are.

Thought stl provides a great way to store functions and call them later it simply not fulfils my requirement - function callbacks should be added and removed runtime. Also the same function shouldn’t be added twice. std::function doesn’t have a comparison operator. You can say that there’s the std::function::target() method that returns a pointer to a callable type. But can we use it with member functions? Recall that in order to create a wrapper around a member function we need to bind it with std::bind():

function<void()> f{ bind(&SomeStruct::someMemberFunc) };

And std::bind() returns an unspecified type. That means that each compiler will have different implementation for this type. Also the standard doesn’t say that instances of this type should be comparable. Take a look at the following code:

SomeStruct obj;

// same function binded twice
function<void()> f{ bind(&SomeStruct::someMemberFunc, obj) };
function<void()> f2{ bind(&SomeStruct::someMemberFunc, obj) };

// get the type of underlying callable object
using RetType = decltype(bind(&SomeStruct::someMemberFunc));

auto a = f.target<T>();
auto b = f2.target<T>();

bool b{ a == b }; // false

Here I binded the same function of the same object to different wrappers. Though can’t compare std::function objects directly, but I can get the underlying callable objects. With Visual Studio I can compare this objects but anyway they are not equal!

Since I can’t compare std::function objects directly I decided to use some sort of hashing. Let’s look at the example:

template<void(*funcPtr)()>
void add()
{
	uintptr_t ptr{ reinterpret_cast<uintptr_t>(funcPtr) };
	string hash{ to_string(ptr) };
}

I took a pointer to a function and converted it to the string - now I have unique key which I can compare. The documentation says:

“Any pointer can be converted to any integral type large enough to hold the value of the pointer (e.g. to std::uintptr_t)”.

Sounds easy, right? But cplusplus wouldn’t be cplusplus if it was so easy. The pointer to member is not an usual pointer and reinterpret_cast doesn’t work with it, i.e. the following code is incorrect:

template<typename T, void(T::*funcPtr)()>
void add()
{
	uintptr_t ptr{ reinterpret_cast<uintptr_t>(funcPtr) }; // cannot convert to 'uintptr_t'
	string hash{ to_string(ptr) };
}

In other words - there’s no simple way to get a pointer to member as uintptr_t and even void*. But we can do some hack:

template<typename T, void(T::*funcPtr)()>
void add()
{
	auto ptr = funcPtr;
	void* ptr2 = &ptr; // get address of the pointer
	char arr[sizeof(uintptr_t)];
	memcpy(arr, ptr2, sizeof(uintptr_t)); // copy the contents of pointer (which is the address to the function)
	uintptr_t ptr3 = *(reinterpret_cast<uintptr_t*>(arr)); // cast to necessary type
}

It can work. But it’s not guaranteed. All this looks like I should forget about hash string. At least at that moment I don’t know how to get string representation of member function robustly. While I’m searching for the way to do it I decided to take the dumbest approach - pass a string tag together with data:

template<void(*funcPtr)()>
void add(const string& tag)
{
}

template<typename T, void(T::*funcPtr)()>
void add(const string& tag)
{
}

It’s not elegant and it adds some complexity to a code but at least it’s robust, haha. Now I can start to implement the Dispatcher class (remember previous post) with std::function.

template<typename T>
class Dispatcher;

template<typename Ret, typename ...Args>
class Dispatcher<Ret(Args...)>
{
public:
	template<Ret(*funcPtr)(Args...)>
	bool add(const string& tag)
	{
		addImpl(tag, funcPtr);
		return true;
	}

	template<typename T>
	bool add(const string& tag, shared_ptr<T> t)
	{
		addImpl(tag, *t.get());
		return true;
	}

	bool remove(const string& tag)
	{
		auto it = find(tags.begin(), tags.end(), tag);
		if (it == tags.end())
		{
			return false;
		}
        
		auto index{ distance(tags.begin(), it) };
		tags.erase(it);
        
		delegates.erase(delegates.begin() + index);
        
		return true;
	}

	void operator()(Args... args)
	{
		for (auto& delegate : delegates)
		{
			delegate(args...);
		};
	}

private:
	bool addImpl(const string& tag, function<Ret(Args...)> delegate)
	{
		if (find(tags.begin(), tags.end(), tag) != tags.end())
		{
			return false;
		}

		delegates.push_back(delegate);
		tags.push_back(tag);

		return true;
	}
    
private:
	vector<function<Ret(Args...)>> delegates;
	vector<string> tags;
};

The addImpl() function accepts a std::function as a second parameter. In different add() functions we’re passing a callable object to it which will be converted to std::function implicitly. Curious reader already noticed that I didn’t provide an implementation for a member function. Why? Because it’s not trivial. Let’s find out why:

struct UserStruct
{
	int member(int a, float b)
	{
		return a + static_cast<int>(b);
	}
};

UserStruct us;
function<int(int, float)> f{ bind(&UserStruct::member, us, placeholders::_1, placeholders::_2) };
f(5, 10.0f);

That’s how we bind and call a member function. Have you noticed std::placeholders? If we don’t know what parameters we’re going to pass to a wrapper - we have to use this stubs. Remember that we’re trying to build “generic” system and we chose to use variadic parameter pack for arguments. Because of this we don’t know the number of placeholders beforehand. And as you have guessed we need to generate them!

After some search we can end up in this documentation and it looks like what we need. And even with an example! It states that we can use our own custom placeholder if we’ll follow certain rules. Let’s try:

template<size_t>
struct MyPlaceholder{};

namespace std
{
	template<>
	struct is_placeholder<MyPlaceholder<1>> : public integral_constant<size_t, 1>{};
    
	template<>
	struct is_placeholder<MyPlaceholder<2>> : public integral_constant<size_t, 2>{};
}

function<int(int, float)> f{ bind(&UserStruct::member, us, MyPlaceholder<1>{}, MyPlaceholder<2>{}) };
f(5, 10.0f)

Wow, it works! But we don’t like this enumeration in std namespace, do we?

template<size_t>
struct MyPlaceholder{};

namespace std
{
	template<size_t N>
	struct is_placeholder<MyPlaceholder<N>> : public integral_constant<size_t, N>{};
}

function<int(int, float)> f{ bind(&UserStruct::member, us, MyPlaceholder<1>{}, MyPlaceholder<2>{}) };
f(5, 10.0f)

Much better. Now we need to remove placeholder’s manual instantiation in std::bind() function. In one of my previous posts I wrote about integer sequence and it seems we can use it here too. Let’s wrap std::bind() and replace our placeholders with a function-generator:

template <size_t N>
MyPlaceholder<N + 1> getPlaceholder()
{
	return {};
}

template <typename T, typename Ret, size_t... Idx, typename... Args>
auto bindImpl(T* obj, Ret(T::*funcPtr)(Args...), index_sequence<Idx...>)
{
    return bind(funcPtr, obj, getPlaceholder<Idx>()...); // getPlaceholder() will be expanded
}

function<int(int, float)> f{ bindImpl(&us, &UserStruct::member, index_sequence_for<int, float>{}) };
f(5, 10.0f)

The getPlaceholder() will receive an integer (starting from 0) as template argument and will be called the number of times equal to the number of callback’s arguments. Exactly as in my previous post. Since placeholders should start from 1 we’re adding +1 in the argument - MyPlaceholder<N + 1>. In our case this code will be generated:

MyPlaceholder<0 + 1> getPlaceholder()
{
	return {};
}

MyPlaceholder<1 + 1> getPlaceholder()
{
	return {};
}

auto bindImpl(UserStruct* obj, int(UserStruct::*funcPtr)(int, float), index_sequence<0, 1>)
{
    return bind(funcPtr, obj, getPlaceholder<0>(), getPlaceholder<1>());
}

Done! Now let’s put it in our Dispatcher:

template<size_t>
struct MyPlaceholder{};

namespace std
{
	template<size_t N>
	struct is_placeholder<MyPlaceholder<N>> : public integral_constant<size_t, N>{};
}

template<typename T>
class Dispatcher;

template<typename Ret, typename ...Args>
class Dispatcher<Ret(Args...)>
{
public:
	// ... other code ...
    
	template<typename T, Ret(T::*funcPtr)(Args...)>
	bool add(const string& tag, shared_ptr<T> obj)
	{
		addImpl(tag, bindImpl(obj.get(), funcPtr, index_sequence_for<Args...>{}));
		return true;
	}
    
	// ... other code ...
    
private:
	// ... other code ...
    
	template <typename T, size_t... Idx>
	function<Ret(Args...)> bindImpl(T* obj, Ret(T::*funcPtr)(Args...), index_sequence<Idx...>)
	{
		return bind(funcPtr, obj, getPlaceholder<Idx>()...);
	}

	template <size_t N>
	MyPlaceholder<N + 1> getPlaceholder()
	{
		return {};
	}
    
	// ... other code ...
}

Noe we can use our dispatcher this way:

Dispatcher<int(int, float)> dispatcher;

auto ptr = make_shared_lambda([](int a, float b)->int
{
	return a + static_cast<int>(b);
});

dispatcher.add("lambda", ptr);
dispatcher.add("lambda", ptr); // will not add because wrapper with this name already binded

auto ptr2 = make_shared<UserStruct>();
dispatcher.add<UserStruct, &UserStruct::member>("member", ptr2);

dispatcher(5, 10.0f);

dispatcher.remove("lambda");
dispatcher.remove("member");

See my previous post for make_shared_lambda() implementation.

Now when we have two implemenations we can compare their performance. I binded a lambda, global function and a member function to both dispatchers and call them 10'000'000 times while measuring the time needed to make this amount of calls. Also I added “raw” function call (direct call without any wrappers). I ran this test 10 times and averaged the result. This is what I got with Visual Studio 2015 (release mode, optimizations):

lambda raw: 21
lambda dispatcher: 235
lambda function dispatcher: 31

global raw: 21
global dispatcher: 85
global function dispatcher: 38

member raw: 22
member dispatcher: 233
member function dispatcher: 38

Sorry, no fancy charts today. The results are interesting and unexpected for me. Delegate version I wrote is incredibly slow but std::function is pretty fast though 50% slower than the raw function call. For me the choice is obvious - I can live with managing unique identifiers for std::function, the speed for me (as a game developer) is much more important.

The source code for both implementations and tests can be found here.

If you like what I do you can buy me a coffee © nikitablack 2021

Powered by Hugo & Kiss.