Ok, I posted a few questions that have recently been linked to ending the C callback API with the 11th C ++ interface. I almost have a satisfactory solution, but I think it may be more elegant and need the help of some template metaprogramming wizards :)
Bear with me, as the sample code is a bit long, but I tried to demonstrate the problem with one shot. Basically, the idea is that, given the list of function pointers and data context pointers, I want to provide a callback mechanism that can be provided with
- Function Pointers
- Function Objects (Functors)
- Lambda
In addition, I want various prototypes to call these functions. I mean, the C API contains about 7 different parameters for a callback, but in most cases, the user code is really only interested in one or two of them. Therefore, I would like the user to be able to specify only those arguments that are of interest to him. (This extends from the point of allowing lambdas first of all ... to provide brevity.)
In this example, the nominal callback C accepts the parameter intand floatand optional float*, which can be used to return some additional data. Thus, the intention of C ++ code is to provide a callback to any of these prototypes in any form that is “callable”. (e.g. functor, lambda, etc.).
int callback2args(int a, float b);
int callback3args(int a, float b, float *c);
Here is my solution.
#include <cstdio>
#include <vector>
#include <functional>
typedef int call2args(int,float);
typedef int call3args(int,float,float*);
typedef std::function<call2args> fcall2args;
typedef std::function<call3args> fcall3args;
typedef int callback(int,float,float*,void*);
typedef std::pair<callback*,void*> cb;
std::vector<cb> callbacks;
template <typename H>
static
int call(int a, float b, float *c, void *user);
template <>
int call<call2args>(int a, float b, float *c, void *user)
{
call2args *h = (call2args*)user;
return (*h)(a, b);
}
template <>
int call<call3args>(int a, float b, float *c, void *user)
{
call3args *h = (call3args*)user;
return (*h)(a, b, c);
}
template <>
int call<fcall2args>(int a, float b, float *c, void *user)
{
fcall2args *h = (fcall2args*)user;
return (*h)(a, b);
}
template <>
int call<fcall3args>(int a, float b, float *c, void *user)
{
fcall3args *h = (fcall3args*)user;
return (*h)(a, b, c);
}
template<typename H>
void add_callback(const H &h)
{
H *j = new H(h);
callbacks.push_back(cb(call<H>, (void*)j));
}
template<>
void add_callback<call2args>(const call2args &h)
{
callbacks.push_back(cb(call<call2args>, (void*)h));
}
template<>
void add_callback<call3args>(const call3args &h)
{
callbacks.push_back(cb(call<call3args>, (void*)h));
}
template<>
void add_callback<fcall2args>(const fcall2args &h)
{
fcall2args *j = new fcall2args(h);
callbacks.push_back(cb(call<fcall2args>, (void*)j));
}
template<>
void add_callback<fcall3args>(const fcall3args &h)
{
fcall3args *j = new fcall3args(h);
callbacks.push_back(cb(call<fcall3args>, (void*)j));
}
int test1(int a, float b)
{
printf("test1 -- a: %d, b: %f", a, b);
return a*b;
}
int test2(int a, float b, float *c)
{
printf("test2 -- a: %d, b: %f", a, b);
*c = a*b;
return a*b;
}
void init()
{
class test3
{
public:
test3(int j) : _j(j) {};
int operator () (int a, float b)
{
printf("test3 -- a: %d, b: %f", a, b);
return a*b*_j;
}
private:
int _j;
};
add_callback(test1);
add_callback(test2);
int j = 5;
add_callback(fcall2args(test3(j)));
add_callback(fcall2args([j](int a, float b)
{
printf("test4 -- a: %d, b: %f", a, b);
return a*b*j;
}));
add_callback(fcall3args([j](int a, float b, float *c)
{
printf("test5 -- a: %d, b: %f", a, b);
*c = a*b*j;
return a*b*j;
}));
}
int main()
{
init();
auto c = callbacks.begin();
while (c!=callbacks.end()) {
float d=0;
int r = c->first(2,3,&d,c->second);
printf(" result: %d (%f)\n", r, d);
c ++;
}
}
, , . , /lambdas std::function inelegant. , , , , . , fcall2args , fcall3args add_callback . , , -.
The second problem is that I, of course, make copies of functor / lambda objects using new, but not deleteusing this memory. I'm not sure the best way would be to keep track of these distributions, although I assume that in a real implementation I could keep track of them in the object of which I am add_callbacka member, and free them in the destructor.Thirdly, I do not find it very elegant to have specific types call2args, call3argsetc. for every variation of the callback I want to allow. This means that I will need a type explosion for each combination of parameters that the user may need. I was hoping there might be some kind of boilerplate solution to make this more general, but I am having problems with this.
. std::vector<std::pair<callback*,void*>> callbacks , . , , , ++ , std::vector, . . .
# 2. , , std::vector<std::pair<callback*,void*>> callbacks . , , , C, :
struct someobject *create_object();
free_object(struct someobject *obj);
add_object_callback(struct someobject *obj, callback *c, void *context);
callback ,
typedef int callback(int a,float b,float *c, void *context);
. , "someobject" - , .. , .
C. , , , , ++ . , ++ lambdas . , ++, :
add_object_callback(struct someobject *obj, func);
func :
, , // :
int cb2args(int a, float b);
int cb2args(int a, float b, float *c);
, , 80% , . , . , function_traits - , . , C, , , ++.