5

Ich möchte Memberfunktionen, die dem Typ 'void (ClassType :: Function) (ArgType)' entsprechen, mit einer Vorlagenklasse umschließen. Später möchte ich eine Instanz von Classtype auf eine Instanz dieser Vorlage passieren und haben es die verpackte Methode aufrufen:C++ - ist es möglich, Klassen- und Argumenttypen aus einem Elementfunktionstyp in einer Vorlage zu extrahieren?

class Foo { 
public: 
    Foo() : f_(0.0) {} 
    void set(double v) { f_ = v * 2.1; } 
    double get() { return f_; } 
private: 
    double f_; 
}; 

template <typename ArgType, typename ClassType, void (ClassType::*Method)(ArgType)> 
class Wrapper { 
public: 
    explicit Wrapper(ClassType *cls) : cls_(cls) {} 

    void do_something(ArgType value) { 
    (cls_->*Method)(value); 
    } 

private: 
    ClassType *cls_; 
}; 

#include <iostream> 
int main(int argc, char ** argv) { 
    Foo foo; 
    Wrapper<double, Foo, &Foo::set> wrapper(&foo); 

    wrapper.do_something(1.0); 
    std::cout << foo.get() << std::endl; 
    // outputs "2.1" 
    return 0; 
} 

Hinweis in der Instanziierung Wrapper <> dass „Foo“ zweimal angegeben ist - es sieht hier überflüssig aus.

Also was ich gerne wissen würde ist, ob es möglich ist, den Vorlagenparameter ClassType zu vermeiden. Wenn es zum Beispiel möglich ist, es aus dem Elementfunktions-Zeigerparameter zu implizieren oder zu extrahieren, dann müsste es nicht explizit in der Instanziierung von Wrapper <> angegeben werden.

In ähnlicher Weise wäre es nützlich zu vermeiden, explizit anzugeben, ArgType auch, wie (vielleicht) kann es aus Foo :: Set ermittelt werden?

Ist das in C++ möglich? Vielleicht etwas in diese (völlig fantastical) Linien:

template <void (ClassType::*Method)(ArgType)> 
class Wrapper2 { 
public: 
    explicit Wrapper(Method::ClassType *cls) : cls_(cls) {} 

    void do_something(Method::ArgType value) { 
    (cls_->*Method)(value); 
    } 

private: 
    Method::ClassType *cls_; 
}; 

// ... 

int main() { 
    Foo foo; 
    Wrapper<&Foo::set> wrapper(&foo); 
    // ... 
} 

Oder vielleicht eine andere Ebene der Vorlage Magie gibt es, die aufgerufen werden kann, dass etwas in dieser Richtung tun würde:

Wrapper<Magic<&Foo::set> > wrapper(&foo); 

Ich bin interessiert an wissen, welche Mechanismen eventuell zur Verfügung stehen.

Ich benutze C++ 03 als Voraussetzung, nicht C++ 11, sondern auch interessiert zu wissen, was C++ 11 bieten könnte.

BEARBEITEN: Weitere Informationen - Ich beabsichtige, diesen Mechanismus zu verwenden, ~ 300 Mitgliedsfunktionen (alle gehören ClassType oder eine Reihe von sehr ähnlichen Klassen) zu wickeln, aber es wird nur rund sechs oder so Signaturen berücksichtigt werden:

  • void (Classtype :: Function) (ArgType) - wo ArgType ist 'schwebenden'
  • void (Classtype :: Function) (ArgType) - wo ArgType ist 'Integral'
  • void (Classtype :: Funktion) (bool)
  • void (ClassType :: Function) (IndexType, ArgType) - die obigen drei wi th extra ‚index‘ Argument

Die Member-Funktionen ‚Setter‘ Funktionen für sind das, was ich „Eigenschaften“ in einer großen Konfiguration ‚Sammlung‘ Klasse, zum Beispiel (eher als die einfach Foo oben) rufen:

+0

Ich bin mir ziemlich sicher, dass es mit einer Klassenspezialisierung möglich sein wird. Da der Abzug umgekehrt funktioniert, könnten Sie den Methodendeklarationstyp in drei Vorlagentypen auflösen, ausgehend von der Basisschablonendeklaration, die nur einen Vorlagentyp enthält. Und puff, extrahiert. Ich werde das sofort in gcc versuchen. –

Antwort

0

Dies ist eine schlechte Re-Implementierung von ::std::mem_fn + ::std::bind, die C++ 11 Konstrukte sind. Hier ist, wie Sie dies mit denen tun:

#include <functional> 

int main() { 
    Foo foo; 
    auto wrapper = ::std::bind(::std::mem_fn(&Foo::set), ::std::ref(foo), _1); 
    wrapper(5); // Calls foo.set(5) 
} 

Aber natürlich möchten Sie eine C++ 03-Lösung. Mit Boost können Sie dies in C++ 03 bekommen. Ich glaube auch, dass so etwas in C++ 03 mit TR1 möglich ist. Sie können sagen, ob Sie das haben, indem Sie sehen, ob #include <tr1/functional> funktioniert. Wenn Sie TR1 haben, erscheinen diese im Namespace ::std::tr1.

Jetzt gibt es eine Möglichkeit, in der es nicht ist. Sie haben den Funktionszeiger selbst zur Typ-Signatur der Klasse gemacht. Das ist eine seltsame Sache, aber sicherlich möglich, wie Sie bereits wissen. Die Möglichkeit, die Werte ClassType und ArgType zur Kompilierzeit zu bestimmen, ist jedoch schwierig. Sie können dies mit dem Template-Funktionsargument-Matching tun, aber nicht sinnvoll, weil C++ 03 nicht auto hat.

+0

Danke, ich schaue mir die Variationen von TR1 und/oder Boost an. Der Grund, warum ich den Funktionszeiger zur Typ-Signatur gemacht habe, liegt darin, dass ich dies erweitern möchte, um Funktionen anderer Typen zu entsprechen, und dass die korrekte Spezialisierung automatisch übereinstimmt. Aber jetzt, wenn ich darüber nachdenke, frage ich mich, ob dies mit einem Argument möglich wäre, das auf den Funktionstyp passt - aber ich würde immer noch ClassType und ArgType als Template-Parameter angeben müssen, damit der Argumenttyp korrekt angegeben werden kann. – meowsqueak

+0

@meowsqueak: Ich habe darüber ein wenig nachgedacht, und solange Sie den Zeigerwert als Vorlage Argument haben, können Sie Ihr Problem in keiner Weise mit Funktionsargumentabgleich lösen. Das Problem ist, dass das Template-Argument ein konstanter Ausdruck sein muss. Und Funktionsargumente sind keine konstanten Ausdrücke. Sie können zwar die Argumentableitung für Vorlagenfunktionen verwenden, um das Argument und den Klassentyp aus einem zufälligen Zeigerwert herauszulösen, Sie können diesen Zeigerwert jedoch nicht als eines der Vorlagenargumente für den Rückgabetyp verwenden. Wenn das überhaupt Sinn macht. – Omnifarious

+0

Ich denke, das macht Sinn. Ich habe meinen Ansatz jetzt geändert - anstatt den Member-Function-Zeiger als Template-Parameter zu verwenden, übergebe ich ihn als normales Argument. Dann habe ich eine kleine Menge von Vorlagen, um mit jeder Signatur umzugehen, die ich einpacken muss. Dies bedeutet, dass die Signatur der gewickelten Funktion (einschließlich des Namens) nicht mehr Teil des Typs ist. Bis jetzt funktioniert es gut. – meowsqueak

0

über Lesen, was du mir von ein paar Optionen haben gemacht denken:

1) Wickeln Sie das Instanziierung in Vererbung. Dies bewegt das gruselige Zeug zu deiner Definition.

class FooWrapper : public Wrapper< double, Foo, &Foo::set >, public Foo 
{ 
public: 
    FooWrapper() : Wrapper(this){} 
}; 

Ihre Logik-Code würde wie folgt aussieht:

FooWrapper fooWrapper; 
    fooWrapper.do_something(1.0); 
    std::cout << fooWrapper.get() << std::endl; 

Was bedeutet, dass Sie die doppelte Vorlage Argumente nicht beseitigen haben, können Sie sie gerade bewegt.

2) Es gibt eine allgemeinere Art und Weise zu wickeln, auf einer Ebene:

template<typename argType1, class classType1> 
class FooWrapper2 : public Wrapper<argType1, classType1, &classType1::set>, public classType1 
{ 
public: 
    FooWrapper2() 
     : classType1(), 
      Wrapper<argType1, classType1, &classType1::set>(this) 
    { 

    } 
}; 

diese Weise die Rückzieh von komplizierteren suchen Logik hat, aber Sie keine neue definieren jedes Mal, Wrapper, nur eine neue Verpackung für jede Unterschrift:

FooWrapper2<double, Foo> fooWrapper2; 
    fooWrapper2.do_something(1.0); 
    std::cout << fooWrapper2.get() << std::endl; 

3) Keeping in Einklang mit der Vorlage Idee, Sie können die Wrapper wickeln:

template<typename argType1> 
class FooWrapper3 : public FooWrapper2<argType1, Foo> 
{ 
public: 
    FooWrapper3() 
    { 

    } 
}; 

Der Logik-Code aus dieser sieht ein bisschen besser, aber Sie haben stecken Sie für jede Art resubclass Sie Verpackung (mit spezifischem Code, anstatt nur unter Verwendung der Schablone):

FooWrapper3<double> fooWrapper3; 
    fooWrapper3.do_something(1.0); 
    std::cout << fooWrapper3.get() << std::endl; 

4) Diese Option Schrott die Basis Wrapper-Klasse und verwendet eine Schnittstelle. Übergeben Sie einfach die Schnittstellen wie bei den Wrappern und Sie können die meisten Aktionen ausführen.

template <typename ArgType> 
class Do_something { 
public: 

    virtual void do_something(ArgType value) = 0; 

}; 

template<typename ArgType> 
class FooWrapper4 : public Foo, public Do_something<ArgType> 
{ 
public: 
    virtual void do_something(ArgType value) 
    { 
     set(1.0); 
    } 
}; 

Das Prüfprogramm ich mit gespielt:

class Foo { 
public: 
    Foo() : f_(0.0) {} 
    void set(double v) { f_ = v * 2.1; } 
    double get() { return f_; } 
private: 
    double f_; 
}; 


template <typename ArgType, typename ClassType, void (ClassType::*Method)(ArgType)> 
class Wrapper { 
public: 
    explicit Wrapper(ClassType *cls) : cls_(cls) {} 

    void do_something(ArgType value) { 
    (cls_->*Method)(value); 
    } 

private: 
    ClassType *cls_; 
}; 


class FooWrapper : public Wrapper< double, Foo, &Foo::set >, public Foo 
{ 
public: 
    FooWrapper() : Wrapper(this){} 
}; 


template<typename argType1, class classType1> 
class FooWrapper2 : public Wrapper<argType1, classType1, &classType1::set>, public classType1 
{ 
public: 
    FooWrapper2() 
     : classType1(), 
      Wrapper<argType1, classType1, &classType1::set>(this) 
    { 

    } 
}; 

template<typename argType1> 
class FooWrapper3 : public FooWrapper2<argType1, Foo> 
{ 
public: 
    FooWrapper3() 
    { 

    } 
}; 

template <typename ArgType> 
class Do_something { 
public: 

    virtual void do_something(ArgType value) = 0; 

}; 

template<typename ArgType> 
class FooWrapper4 : public Foo, public Do_something<ArgType> 
{ 
public: 
    virtual void do_something(ArgType value) 
    { 
     set(1.0); 
    } 
}; 

#include <iostream> 
int main(int argc, char ** argv) { 
    Foo foo; 
    Wrapper<double, Foo, &Foo::set> wrapper(&foo); 

    wrapper.do_something(1.0); 
    std::cout << foo.get() << std::endl; 

    FooWrapper fooWrapper; 
    fooWrapper.do_something(1.0); 
    std::cout << fooWrapper.get() << std::endl; 
    // outputs "2.1" 

    FooWrapper2<double, Foo> fooWrapper2; 
    fooWrapper2.do_something(1.0); 
    std::cout << fooWrapper2.get() << std::endl; 

    FooWrapper3<double> fooWrapper3; 
    fooWrapper3.do_something(1.0); 
    std::cout << fooWrapper3.get() << std::endl; 

    FooWrapper4<double> fooWrapper4; 
    fooWrapper4.do_something(1.0); 
    std::cout << fooWrapper4.get() << std::endl; 

    return 0; 
} 
+0

Liebe Menschen der Zukunft, ich kann nicht scheinen, dass die Formatierung auf einem der Codesegmente richtig funktioniert, entschuldige mich. –

+0

Danke für die Antwort. Bezüglich Punkt 1 muss ich dies erweitern, um über 300 Mitgliedsfunktionen (Teil einer Konfigurationsschnittstelle von einer dritten Partei) zu wickeln, wissend, dass es nur ungefähr sechs verschiedene Methodensignaturen gibt. Wenn man die Vererbung für jeden einzelnen eingibt, werden 300 separate Klassen geschrieben, es sei denn, ich temple sie (das bringt mir am Ende nichts). – meowsqueak

+0

@meowsqueak, da das der Fall ist, sollte Methode 2 Ihnen geben, was Sie suchen. Sie sollten nur 6 davon definieren müssen. –

2

In C++ Sie könnten lambdas verwenden, wie:

template <typename X, typename ARG> 
std::function<void(X*, ARG)> wrapper(void (X::*mfp)(ARG)) 
{ 
    return [=](X *x, ARG arg) { 
     (x->*mfp)(arg); 
    }; 
} 

Mit VisualC++ (zumindest als Zuletzt als VS2013), verwenden Sie die Erfassung nach Wert [=], wenn Sie Elementfunktionszeiger erfassen (oder Abstürze erleben).

Spielplatz:

#include <iostream> 
#include <functional> 

struct A { 
    virtual void a(int i) { std::cout << "A: " << i << std::endl; } 
}; 

struct B { 
    virtual void b(int i) { std::cout << "B: " << i << std::endl; } 
}; 

template <typename X, typename ARG> 
std::function<void(X*, ARG)> wrapper(void (X::*mfp)(ARG)) { 
    return [=](X *x, ARG arg) { (x->*mfp)(arg); }; 
} 

int main() 
{ 
    auto g = wrapper(&B::b); 
    B b; 
    g(&b, 3); 
    auto h = wrapper(&A::a); 
    A a; 
    h(&a, 4); 
    return 0; 
} 
2
struct MyClass 
{ 
    MyClass& Move(MyClass& m) { return *this; } 
}; 

typedef MyClass& (MyClass::*MethodT) (MyClass&); 

template< typename T > 
struct ExtractType : std::false_type 
{ 
}; 

template< typename R, typename C, typename A > 
struct ExtractType< R (C::*)(A) > 
{ 
    typedef C type; 
}; 

static_assert(std::is_same< ExtractType<MethodT>::type, MyClass >::value, "oops"); 

Es scheint 4.8 in meiner Version von gcc zu arbeiten.
Es funktioniert, wie ich im Kommentar erwähnt, es ist ein "Back-Muster-Matching", dass der Compiler während der Spezialisierung überprüft. Das ist sehr mächtig.
Sie sehen also, dass wir eine Art von Muster angegeben haben, die vom Compiler in den drei Subtypen zerlegt wird, die ihn bilden: T, wenn der Typ T ist: R, C, A. Was ist Rückgabetyp, Klassentyp und Argument?

Sie können jedoch sehen, dass es mit einem Argument funktioniert. Wie geht es, wenn wir eine undefinierte Anzahl von Argumenten haben?
Vielleicht eine Liste von Checker-Klassen, oder verwenden Sie variadic Vorlagen?

Und ehrlich gesagt bin ich nicht einmal sicher, ob das mit void funktioniert. Ich denke, Void ist immer unmöglich in der Vorlage zu platzieren, daher wird es viele Versionen dieser ExtractType Klasse geben, um alle Kombinationen von möglichen Deklarationen zu unterstützen. Oder so scheint es mir.

EDIT:

Ok, damit ich völlig diese weg zufällig bin zu geben, aber es scheint, in C++ 11 es viel besser funktioniert, als ich erwartet habe, ist dies in Ordnung, auf gcc 4.8:

struct MyClass 
{ 
}; 

typedef int (MyClass::*MethodT) (bool); 
typedef void (MyClass::*VV)(); 
typedef void (MyClass::*IL) (int, long); 

template< typename T > 
struct ExtractType : std::false_type 
{ 
}; 

template< typename R, typename C, class...A > 
struct ExtractType< R (C::*)(A...) > 
{ 
    typedef C type; 
    typedef R returntype; 
}; 

static_assert(std::is_same< ExtractType<MethodT>::type, MyClass >::value, "oops"); 
static_assert(std::is_same< ExtractType<VV>::type, MyClass >::value, "oops"); 
static_assert(std::is_same< ExtractType<IL>::type, MyClass >::value, "oops"); 

static_assert(std::is_same< ExtractType<MethodT>::returntype, int >::value, "oops"); 
static_assert(std::is_same< ExtractType<VV>::returntype, void >::value, "oops"); 
static_assert(std::is_same< ExtractType<IL>::returntype, void >::value, "oops"); 

Der verrückte Teil ist, dass es void im Rückgabetyp nichts ausmacht. Natürlich ist es aber C++ 11.

Verwandte Themen