2017-07-11 1 views
6

Ich habe die letzten paar Tage damit verbracht, einen generalisierten Wrapper für Funktionszeiger in C++ zu erstellen, und es ist mir gelungen, fast jeden einzelnen Fehlerbalken zu lösen. Mein Hauptziel dabei war, ein Objekt einfach als Funktion mit einem intern gespeicherten Funktionszeiger aufrufen zu können. Wenn der Zeiger irgendwo hinzeigen würde, würde er es als normal bezeichnen, während ein Nullzeiger die Funktion einfach nicht aufrufen würde, und es würde weiterlaufen, als ob nichts passiert wäre. Ich beabsichtige, dies hauptsächlich für Rückruffunktionszwecke zu verwenden, wo es mir wahrscheinlich egal wäre, ob die Funktion aufgerufen wurde oder nicht und nur eine Aktion ausführen wollte. Es funktioniert fast perfekt mit den folgenden:Verwendung von Cs '...' Parameter Pack als C++ Template Wert?

template<typename T> 
class Action; 

template<typename TReturn, typename ... TArgs> 
class Action<TReturn(TArgs...)> { 
public: 
    //! Define a type for the Action object 
    typedef TReturn(*signature)(TArgs...); 

    //! Constructors 
    inline Action(const signature& pFunc = nullptr) : mPointer(pFunc) {} 
    inline Action(const Action& pCopy) : mPointer(pCopy.mPointer) {} 

    //! Operator Call 
    inline bool operator() (TReturn& pReturn, TArgs ... pArgs) const { if (!mPointer) return false; pReturn = mPointer(pArgs...); return true; } 

    //! Operators 
    inline Action& operator=(const Action& pCopy) { mPointer = pCopy.mPointer; return *this; } 
    inline Action& operator=(const signature& pFunc) { mPointer = pFunc; return *this; } 
    inline operator bool() const { return (mPointer != nullptr); } 

private: 
    //! Store a pointer to the callback function 
    signature mPointer; 
}; 

template<typename ... TArgs> 
class Action<void(TArgs...)> { 
public: 
    //! Define a type for the Action object 
    typedef void(*signature)(TArgs...); 

    //! Constructors 
    inline Action(const signature& pFunc = nullptr) : mPointer(pFunc) {} 
    inline Action(const Action& pCopy) : mPointer(pCopy.mPointer) {} 

    //! Operator Call 
    inline bool operator() (TArgs ... pArgs) const { if (!mPointer) return false; mPointer(pArgs...); return true; } 

    //! Operators 
    inline Action& operator=(const Action& pCopy) { mPointer = pCopy.mPointer; return *this; } 
    inline Action& operator=(const signature& pFunc) { mPointer = pFunc; return *this; } 
    inline operator bool() const { return (mPointer != nullptr); } 

private: 
    //! Store a pointer to the callback function 
    signature mPointer; 
}; 

Aber ich wie die Situation spüren, würde höchstwahrscheinlich Verwendung dieser Wrapper die Ausgabe von Debug-Informationen oder formatierten Text. Dies kann durch benutzerdefinierte Funktionen oder eingebaute Funktionen wie printf geschehen. Um mit printf Unterschrift übereinstimmen würde eine Aktion wie erstellt werden:

Action<int(const char*, ...)> callback = printf; 

und es wäre in der Lage, die gleiche Art und Weise zu betreiben, dass jede andere Aktion verhalten würde. Das Problem, das ich finde, ist das '...' wird dazu führen, dass die Vorlagensignatur nicht mit einer der Spezialisierungen übereinstimmt, sondern mit der ersten, die nur ein Prototyp ist.

Ich kann vollständig verstehen, warum das nicht funktioniert und warum der Compiler nicht in der Lage wäre, die Generierung der erforderlichen Klasse zu bewältigen, aber ich hoffte, dass jemand hier irgendwelche hinterhältigen Wege kennen würde, um dies oder etwas ähnliches zu erreichen. Jede Hilfe wäre sehr geschätzt, Dank :)

+7

Warum nicht 'std :: function' verwenden? – StoryTeller

+0

Was ist der Zweck von "Aktion"? Welche Voraussetzung haben Sie, dass Sie ['std :: function'] (http://en.cppreference.com/w/cpp/utility/functional/function) nicht verwenden können? Welches Problem löst 'Action', dass' std :: function' nicht funktioniert? –

+1

https://stackoverflow.com/questions/18370396/why-cant-stdfunction-bind-to-c-style-varijadic-functions gab einen Kommentar, wie man das fast erreichen kann. Ich denke für jede variadic Funktion, Sie würden eine konkrete Klasse benötigen, um Ihnen zu helfen, und hoffen, dass es eine v * Variante der Funktion gab, die Sie nennen konnten. – mksteve

Antwort

1

Das folgende Beispiel funktioniert für alle Funktionstypen und Lambda-Ausdrücke ohne capture:

#include <utility> 
#include <cstdio> 
#include <cmath> 

template<typename Fn> 
class Action { 
    Fn* function_ptr;   
public: 
    Action() noexcept : function_ptr(nullptr) {}  
    Action(std::nullptr_t) noexcept : function_ptr(nullptr) {}  
    Action(const Action& other) : function_ptr(other.function_ptr) {}  
    Action(Fn f) : function_ptr(f) {} 

    Action& operator=(const Action& other) { 
     return (function_ptr = other.function_ptr, *this); 
    }    
    Action& operator=(std::nullptr_t) { 
     return (function_ptr = nullptr, *this); 
    }  
    Action& operator=(Fn f) { 
     return (function_ptr = f, *this); 
    } 

    template<typename... Params> 
    auto operator()(Params&&... params) { 
     return function_ptr(std::forward<Params>(params)...); 
    } 
}; 

Live Demo

Wie die Ausführungen unter Ihrer Frage erwähnt Mit std::function ist es besser als einen Wrapper für Funktionszeiger zu schreiben. Das einzige Problem mit std::function ist, dass es nicht mit Funktionssignaturen verwendet werden kann, die Auslassungspunkte enthalten. Wenn Sie die Signatur explizit angeben, können Sie z. printf wie folgt:

std::function<int(const char*, int, double, double)> fn = printf; 

Wenn Sie ++ 17 C verwenden oder Boost, Sie können Sie printf -ähnliche Funktion implementieren besitzen, std::any oder Boost.Any verwendet, die zu std::function zugeordnet werden können wie folgt:

#include <iostream> 
#include <string> 
#include <vector> 
#include <any> 
#include <functional> 

using namespace std; 

void any_printf(string&& format, vector<any>&& args) { 
    int arg_index = 0; enum { NORMAL, CONVERT } mode; 

    for(auto& chr : format) { 
     if(mode == CONVERT) { 
      switch(chr) { 
      case 'd': cout << any_cast<int>(args[arg_index++]); break; 
      case 'f': cout << any_cast<float>(args[arg_index++]); break; 
      case 's': cout << any_cast<string>(args[arg_index++]); break; 
      /* ... */ 
      default: cout << chr; 
      }; 
      mode = NORMAL; 
     } 
     else { 
      chr == '%' ? (mode = CONVERT, 0) : (cout << chr, 0); 
     } 
    } 
} 

int main() { 
    using namespace string_literals; 
    function<void(string&&, vector<any>&&)> f_ptr { any_printf }; 

    f_ptr("Cuboid: %smm x %dmm x %fmm.\n", { any("3"s), any(4), any(6.67f) }); 
    return 0; 
} 
+0

Das ist ein interessanter Weg, um es zur Arbeit zu bringen.Mein einziges Problem ist, dass ein Wert existiert, um die Signatur für die Vorlage zu erhalten. Es ist nicht so einfach für die einfache Verlangsamung von gewünschten Funktionsrückrufen, ohne wenigstens eine einzige Funktion zu berücksichtigen, um der Situation zu entsprechen. Ich hatte geahnt, dass die genaue Art der Syntax, nach der ich suchte, nahe, wenn nicht unmöglich wäre. Danke für die schnelle Antwort :) – MitchellCroft

+1

@ MitchellCroft, wenn Sie eine Funktion schreiben, die einen Rückruf aufrufen wird, müssen Sie die Signatur davon definieren. Wenn Sie möchten, ** diesen Callback mit variabler Anzahl von Daten und mit Variablentypen ** aufzurufen (wie 'printf' aufgerufen werden kann), können Sie z.B. ein 'std :: vector' von [' std :: any'] (http://en.cppreference.com/w/cpp/utility/any) Typ. Dies ist eine C++ 17-Funktion, aber [Boost.Any] (http://www.boost.org/doc/libs/1_64_0/doc/html/any.html) kann auch als Ersatz verwendet werden. – Akira

+0

@MitchellCroft, habe ich meine Antwort mit dem 'std :: any'-Ansatz bearbeitet, den ich im vorherigen Kommentar erwähnt habe. – Akira

Verwandte Themen