How does the template parameter of std::function work? (implementation)
After getting help from other answers and comments, and reading GCC source code and C++11 standard, I found that it is possible to parse a function type (its return type and its argument types) by using partial template specialization and function overloading.
The following is a simple (and incomplete) example to implement something like std::function
:
template<class T> class Function { };
// Parse the function type
template<class Res, class Obj, class... ArgTypes>
class Function<Res (Obj*, ArgTypes...)> {
union Pointers {
Res (*func)(Obj*, ArgTypes...);
Res (Obj::*mem_func)(ArgTypes...);
};
typedef Res Callback(Pointers&, Obj&, ArgTypes...);
Pointers ptrs;
Callback* callback;
static Res call_func(Pointers& ptrs, Obj& obj, ArgTypes... args) {
return (*ptrs.func)(&obj, args...);
}
static Res call_mem_func(Pointers& ptrs, Obj& obj, ArgTypes... args) {
return (obj.*(ptrs.mem_func))(args...);
}
public:
Function() : callback(0) { }
// Parse the function type
Function(Res (*func)(Obj*, ArgTypes...)) {
ptrs.func = func;
callback = &call_func;
}
// Parse the function type
Function(Res (Obj::*mem_func)(ArgTypes...)) {
ptrs.mem_func = mem_func;
callback = &call_mem_func;
}
Function(const Function& function) {
ptrs = function.ptrs;
callback = function.callback;
}
Function& operator=(const Function& function) {
ptrs = function.ptrs;
callback = function.callback;
return *this;
}
Res operator()(Obj& obj, ArgTypes... args) {
if(callback == 0) throw 0; // throw an exception
return (*callback)(ptrs, obj, args...);
}
};
Usage:
#include <iostream>
struct Funny {
void print(int i) {
std::cout << "void (Funny::*)(int): " << i << std::endl;
}
};
void print(Funny* funny, int i) {
std::cout << "void (*)(Funny*, int): " << i << std::endl;
}
int main(int argc, char** argv) {
Funny funny;
Function<void(Funny*, int)> wmw;
wmw = &Funny::print; // void (Funny::*)(int)
wmw(funny, 10); // void (Funny::*)(int)
wmw = &print; // void (*)(Funny*, int)
wmw(funny, 8); // void (*)(Funny*, int)
return 0;
}
How it does it (I believe) is left undefined (but I don't have a copy of the standard here).
But given all the different possibilities that need to be covered I have the feeling that deciphering the exact definition of how it works would be really hard: So I am not going to try.
But I think you would like to know how functors work and they are relatively simple. So here is a quick example.
Functors:
These are objects that act like functions.
They are very useful in template code as they often allow you to use objects or functions interchangeably. The great thing about functors though is that they can hold state (a sort of poor man's closure).
struct X
{
int operator()(int x) { return doStuff(x+1);}
int doStuff(int x) { return x+1;}
};
X x; // You can now use x like a function
int a = x(5);
You can use the fact that functor hold state to hold things like parameters or the objects or the pointer to member methods (or any combination thereof).
struct Y // Hold a member function pointer
{
int (X::*member)(int x);
int operator(X* obj, int param) { return (obj->*member)(param);}
};
X x;
Y y;
y.member = &X::doStuff;
int a = y(&x,5);
Or even go further and bind parameters. So now all you need to provide is one of the parameters.
struct Z
{
int (X::*member)(int x);
int param;
Z(int (X::*m)(int), int p) : member(m), param(p) {}
int operator()(X* obj) { return (obj->*member)(param);}
int operator()(X& obj) { return (obj.*member)(param);}
};
Z z(&X::doStuff,5);
X x;
int a = z(x);
To answer the question in the title. The parameter that std::function
uses is a nice trick to pass many type parameters as a single template parameter. Those arguments being the argument types and the return type of a function.
It turns out that std::function
tries to type-erase a general functor but that is just coincidence.
As a matter of fact, once upon a time there were compilers that wouldn't accept such tricks and the boost::function
precursor had a portable syntax by which all the parameters could be passed separately:
Preferred syntax
boost::function<void(int*, int, int&, float&)> sum_avg;
Portable syntax
boost::function4<void, int*, int, int&, float&> sum_avg;
https://www.boost.org/doc/libs/1_68_0/doc/html/function/tutorial.html#id-1.3.16.5.4
So that's how the template parameters of std::function
work, at the end it is just a trick to make a lot of parameters look like a function call. Function pointers to that type of function are not necessarily involved in the class.