2010-03-20 20 views
5

Extendsdevoluciones de llamada genérica

Related

lo tanto, estoy tratando de aprender metaprogramming plantilla mejor y resolver esto es un buen ejercicio para ello.

Estoy tratando de escribir código que pueda devolver una función con cualquier cantidad de argumentos que me gusten pasarle.

 
// First function to call 
int add(int x, int y) ; 

// Second function to call 
double square(double x) ; 

// Third func to call 
void go() ; 

La devolución de llamada código de creación debe ser similar:

 
// Write a callback object that 
// will be executed after 42ms for "add" 
Callback<int, int, int> c1 ; 
c1.func = add ; 
c1.args.push_back(2); // these are the 2 args 
c1.args.push_back(5); // to pass to the "add" function 
         // when it is called 

Callback<double, double> c2 ; 
c2.func = square ; 
c2.args.push_back(52.2) ; 

Lo que estoy pensando es decir, utilizando la plantilla metaprogramming Quiero ser capaz de declarar como devoluciones de llamada, escribir una estructura como esta (por favor, tenga en cuenta que es muy pseudocódigo)

 
<TEMPLATING ACTION <<ANY NUMBER OF TYPES GO HERE>> > 
struct Callback 
{ 
    double execTime ; // when to execute 
    TYPE1 (*func)(TYPE2 a, TYPE3 b) ; 

    void* argList ; // a stored list of arguments 
         // to plug in when it is time to call __func__ 
} ; 

Así que para cuando se le llama con

 
Callback<int, int, int> c1 ; 

Automáticamente se obtendría construido para usted por < PORNO DE PLANTILLAS DE ACCIÓN > una estructura como

 
struct Callback 
{ 
    double execTime ; // when to execute 
    int (*func)(int a, int b) ; 

    void* argList ; // this would still be void*, 
         // but I somehow need to remember 
         // the types of the args.. 
} ; 

Cualquier punteros en la dirección correcta para empezar a trabajar en la redacción de este?

Respuesta

1

Mire boost::bind. Tengo poco más que decir ... el tiempo probablemente sea el mejor dedicado a examinar su fuente y tratar de volver a implementarlo, si realmente quieres entender las partes internas. Pero dado lo bien que lo han pulido, la reimplementación es solo una actividad académica.

2

Puede hacer esto con variadic templates, que su compilador puede no ser compatible. Nunca los utilicé yo mismo y, por lo tanto, es posible que algunos de los detalles estén equivocados, pero trataré de describirlos.

Las plantillas variables utilizan el operador "...". Dentro de una declaración de plantilla (u otras expresiones de tipo), las elipsis indican que el parámetro formal puede tomar cualquier cantidad de argumentos.

template <typename ... Args> 
class Variadic { 
public: 
    operator()(Args&& ... args); 
}; 

Dentro de una expresión de llamada de función, los elipses descomprimen su argumento de la izquierda.

Variadic<Args>::operator(Args&& ... args) { 
    func(args...); 
} 

Para reenviar, puede que tenga que utilizar std::forward; esta es un área donde mi conocimiento se vuelve borroso. Poner esto juntos, y obtenemos:

template <typename ReturnValue, typename ... Args> 
class Callback { 
    typedef ReturnValue (*Func)(Args ... args); 

    double execTime; 
    Func func; 
    Args... args; 

public: 
    Callback(double et, Func f) : execTime(et), func(f) {} 
    ReturnValue operator()(Args&& ... a); 
    ReturnValue operator()(); 
}; 

template <typename ReturnValue, typename ... Args> 
ReturnValue Callback<ReturnValue, Args>::operator()(Args&& ... a) { 
    return (*func)(std::forward(a)...); 
} 
template <typename ReturnValue, typename ... Args> 
ReturnValue Callback<ReturnValue, Args>::operator()() { 
    return operator(*func)(args...); 
} 
+1

Las plantillas variables son parte de C++ 0x. Un compilador que los respalde (o incluso un compilador con menos compatibilidad con C++ 0x) también incluirá objetos de función C++ 0x y 'std :: bind', que es similar a' boost :: bind' e implementa lo que eres hablando sobre. – Potatoswatter

0

C++ 0x añade plantillas variadic, que apoyan directamente una plantilla que tiene un número arbitrario de parámetros. Sin eso, puedes usar la especialización parcial para simularlo, aunque requiere una especialización separada para cada número de parámetros. Por ejemplo, se puede apoyar de 1 a 3 parámetros de la siguiente manera:

class null_class {}; 

template <class func, class arg1, class arg2, class arg3> 
class callback_t { 
    func f; 
    arg1 a; 
    arg2 b; 
    arg3 c; 
public: 
    callback_t(func f, arg1 a, arg2 b, arg3 c) : f(f), a(a), b(b), c(c) {} 
    double operator()() const { return f(a, b, c); } 
}; 

template <class func, class arg1, class arg2> 
class callback_t<func, arg1, arg2, null_class> { 
    func f; 
    arg1 a; 
    arg2 b; 
public: 
    callback_t(func f, arg1 a, arg2 b) : f(f), a(a), b(b) {} 
    double operator()() const { return f(a, b); } 
}; 

template <class func, class arg1> 
class callback_t<func, arg1, null_class, null_class> { 
    func f; 
    arg1 a; 
public: 
    callback_t(func f, arg1 a) : f(f), a(a) {} 
    double operator()() const { return f(a); } 
}; 

template <class func, class arg1, class arg2, class arg3> 
callback_t<func, arg1, arg2, arg3> 
callback(func f, arg1 a, arg2 b, arg3 c) { 
    return callback_t<func, arg1, arg2, arg3>(f, a, b, c); 
} 

template <class func, class arg1, class arg2> 
callback_t<func, arg1, arg2, null_class> 
callback(func f, arg1 a, arg2 b) { 
    return callback_t<func, arg1, arg2, null_class>(f, a, b); 
} 

template <class func, class arg> 
callback_t<func, arg, null_class, null_class> 
callback(func f, arg a) { 
    return callback_t<func, arg, null_class, null_class>(f, a); 
} 

#ifdef TEST 
#include <iostream> 

double square(double d) { 
    return d * d; 
} 

double add(double a, double b) { 
    return a + b; 
} 

double sum(double a, double b, double c) { 
    return a + b + c; 
} 

int main() { 
    double a = 2.0, b = 3.0, c=4.0; 

    double d = callback(square, a)(); 
    double e = callback(add, b, c)(); 
    double f = callback(sum, a, b, c)(); 

    std::cout << "2.0 squared = " << d << "\n"; 
    std::cout << "3.0 + 4.0 = " << e << "\n"; 
    std::cout << "Sum = " << f << "\n"; 
    return 0; 
} 

#endif 

El tipo de retorno puede ser de plantilla también, pero he dejado que por el bien de la simplicidad (o al menos se reduce la complejidad).

0

Para empezar, usted debe comprobar fuera de Boost.Function, ya que se trata de funciones que envuelven automáticamente, se le dará ideas que pienso;)

segundo lugar, su sintaxis es un poco incómodo. Se puede utilizar perfectamente las funciones de firmas como parte ya que los parámetros de plantilla, que muy bien se ocupa del problema de plantillas variadic ya que le permite pasar un número arbitrario de tipos;)

Callback< int(int,int) > callback; 

indicaría que su devolución de llamada tomará una puntero a una función con una firma similar a su add: int add(int, int). En realidad, prefiero esta sintaxis, ya que deja más claro lo que estamos transmitiendo.

Antes de comenzar, tengo una pregunta: ¿qué desea hacer con el tipo de devolución?

1. Referencias

Entonces hay cosas como la biblioteca Boost.Fusion que podría ayudar a que una gran cantidad (básicamente, tuplas).

Además, consulte Boost.FunctionTypes que ofrece instalaciones para analizar la firma de una función.

2. En el camino otra vez

// It is nice to have a base class 
// Generally callbacks do not return anything though... 
struct CallbackBase 
{ 
    virtual ~CallbackBase(); 
    virtual void execute() const = 0; 
}; 

namespace func_ = boost::function_types; 

template < 
    class Parameters, 
    class N = typename mpl_::minus< 
    typename mpl_::size<Parameters>::type, 
    mpl_::size_t<1> 
    >::type 
> 
class Streamer 
{ 
public: 
    typedef Streamer< Parameters, typename mpl_::minus<N,1>::type > next_type; 
    typedef typename mpl_::size<Parameters>::type size_type; 
    typedef typename mpl_::minus< size_type, N >::type index_type; 
    typedef typename mpl_::at<Parameters, index_type>::type arg_type; 

    Streamer(Parameters& p): mParameters(p) {} 

    next_type operator<<(arg_type arg) 
    { 
    boost::fusion::at_c<index_type>(mParameters) = arg; 
    return next_type(mParameters); 
    } 

private: 
    Parameters& mParameters; 
}; 

template <class Parameters> 
struct Streamer<Paramaters,0> 
{ 
    Streamer(Parameters&) {} 
}; 


template <class Function> 
class Callback: public CallbackBase 
{ 
public: 
    typedef typename func_::result_type<Function>::type result_type; 
    typedef typename func_::parameters_type<Function>::type parameters_type; 
    typedef typename func_::function_pointer< 
    typename func_::components<Function>::type 
    >::type function_pointer; 

    Callback(function_pointer f): mFunction(f) {} 

    virtual void execute() const 
    { 
    mReturn = Invoke<function_pointer>::Do(f,mParameters); 
    } 

    Streamer<parameters_type> operator<<(typename mpl_::at<parameters_type, 0>::type arg) 
    { 
    boost::fusion::at_c<0>(mParameters) = arg; 
    return Streamer<parameters_type>(mParameters); 
    } 

private: 
    function_pointer f; 
    result_type mResult; 
    parameters_type mParameters; 
}; 

Bueno, eso es lo lejos que fui. No he tratado la invocación real que requiere desempaquetar la tupla para pasar los argumentos a la función.

uso Hasta ahora sería:

int add(int,int); 

void pass(const CallbackBase& b); 

int main(int argc, char* argv[]) 
{ 
    Callback<int(int,int)> c(&add); 
    c << 2 << 4; 
    pass(c); 
} 

le animo fuertemente a ahondar en Boost.Fusion si desea seguir sus estudios en este campo, como metaprogramming plantilla pura es a menudo inútil si no puede llevar el resultado al mundo del tiempo de ejecución :)

Cuestiones relacionadas