2010-03-20 16 views
5

ExtendsGenerisches Rückrufe

Related

Also, ich versuche, besser Metaprogrammierung zu lernen, und ich finde, dies ist es eine gute Übung.

Ich versuche Code zu schreiben, der eine Funktion mit einer beliebigen Anzahl von Argumenten zurückrufen kann, die ich gerne weitergegeben habe.

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

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

// Third func to call 
void go() ; 

Die Erstellung Code Rückruf sollte wie folgt aussehen:

 
// 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) ; 

Was ich denke, ist, mit Metaprogrammierung Ich möchte in der Lage sein, Rückrufe wie zu erklären, eine Struktur wie folgt schreiben (bitte denken Sie daran, das ist sehr Pseudo-Code)

 
<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__ 
} ; 

Also für beim Aufruf mit

 
Callback<int, int, int> c1 ; 

Sie automatisch für Sie durch < HARDCORE Templating ACTION > eine Struktur wie

 
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.. 
} ; 

Alle Zeiger in die richtige Richtung, um loszulegen auf das Schreiben dieses erhalten gebaut würde?

Antwort

1

Betrachten Sie boost::bind. Ich habe wenig mehr zu sagen ... Die Zeit ist wahrscheinlich am besten damit verbracht, über ihre Quelle nachzudenken und sie neu zu implementieren, wenn Sie die Interna wirklich verstehen wollen. Aber, wie gut sie es poliert haben, ist Reimplementierung nur eine akademische Verfolgung.

2

Sie können dies mit variadic templates tun, die Ihr Compiler möglicherweise nicht unterstützt. Ich habe sie nie selbst benutzt und kann daher einige Details falsch verstehen, aber ich werde versuchen, sie zu beschreiben.

Variadic-Vorlagen verwenden den Operator "...". In einer Schablonenklaration (oder anderen Typen von Ausdrücken) weisen Ellipsen darauf hin, dass der formale Parameter eine beliebige Anzahl von Argumenten annehmen kann.

Innerhalb eines Funktionsaufrufausdrucks entpacken Ellipsen ihr linkes Argument.

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

So leiten, könnten Sie std::forward verwenden müssen; Dies ist ein Bereich, in dem mein Wissen unscharf wird. Setzen Sie das zusammen, und wir erhalten:

+1

Variadic Templates sind Teil von C++ 0x. Ein Compiler, der sie unterstützt (oder sogar einen Compiler mit weniger C++ 0x Unterstützung), wird auch C++ 0x Funktionsobjekte und 'std :: bind' enthalten, was ähnlich wie boost :: bind ist und implementiert, was Sie sind sprechen über. – Potatoswatter

0

C++ 0x fügt variadic Vorlagen, die eine Vorlage, die eine beliebige Anzahl von Parametern akzeptiert direkt unterstützt. Ohne dies können Sie eine teilweise Spezialisierung verwenden, um es zu simulieren, obwohl es eine separate Spezialisierung für jede Anzahl von Parametern erfordert. Zum Beispiel könnten Sie von 1 bis 3 Parameter wie folgt unterstützen:

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 

Der Rückgabetyp als auch als Templat werden kann, aber ich habe, dass aus Gründen der Einfachheit weggelassen (oder zumindest die Komplexität reduziert).

0

Um damit zu beginnen, Sie Boost.Function sollten überprüfen, da es sich um Einwickeln Funktionen automatisch, es wird Ihnen Ideen geben denke ich;)

Zweitens Ihre Syntax ist ein wenig umständlich. Sie können perfekt Funktionen Signaturen als Teil wie die Template-Parameter verwenden, die gut mit dem Problem der variadische Vorlagen befasst, da es Ihnen, eine beliebige Anzahl von Arten passieren lässt;)

Callback< int(int,int) > callback; 

würde bedeuten, dass Ihr Rückruf ein nehmen Zeiger auf eine Funktion mit einer ähnlichen Signatur wie Ihre add: int add(int, int). Ich bevorzuge diese Syntax, da sie deutlicher macht, was wir weitergeben.

Bevor wir anfangen, habe ich eine Frage: Was wollen Sie mit dem Rückgabetyp machen?

1. Referenzen

Dann gibt es Dinge wie die Boost.Fusion Bibliothek, die Ihnen helfen, eine Menge könnte (im Grunde, Tupel).

Überprüfen Sie auch Boost.FunctionTypes, die Einrichtungen zur Analyse einer Funktionssignatur bietet. wieder

2. Auf dem Weg

// 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; 
}; 

Nun, das ist, wie weit ich ging. Ich habe mich nicht mit dem tatsächlichen Aufruf beschäftigt, der das Entpacken des Tupels erfordert, um die Argumente an die Funktion zu übergeben.

Bisher Nutzung wäre:

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); 
} 

ich Sie dringend ermutigen, in Boost.Fusion zu vertiefen, wenn Sie Ihre Studien in diesem Bereich zu verfolgen, als reine Metaprogrammierung oft unfruchtbar ist, wenn man nicht bringe das Ergebnis in die Laufzeitwelt :)