Rückgabewerte in allgemeine Form wrappen; was macht man wegen void?
-
Guten Abend,
Vornweg sei gesagt, dass das, was hier zu versuchen ist, ein reines Experiment darstellt. C++ verkommt immer mehr zu einer Mathematik-artigen Sprache, wo man Dinge ausprobieren kann
Angeommen, man möchte Funktionen jeglichen Rückgabetyps in eine allgemeine Form wrappen, so dass alle die gleiche Signatur aufweisen und denselben Wrapper (hier sei mal
boost::any
gesetzt) zurückgeben. Hierzu gegeben ist folgender Code, wobei man mit Hilfe voninvoke
undbind
einen Funktions-Wrapper erzeugen kann:template<typename result_type> result_type get_default() { return result_type(); } template<typename result_type> any invoke(result_type (*fimpl)()) { return fimpl(); } // Verwendung: any result = invoke(&get_default<int>); // Später: result = invoke(&get_default<string>);
Wie realisiert man jedoch den Sonderfall, wo die Funktion einen
void
-Rückgabewert hat, also nichts zurück gibt? Die Varianteinvoke<void>
funktioniert nicht, da ja nichts zurückgegeben wird.Ich habe eine - zugegeben extrem hässliche - Möglichkeit gefunden, keine Sonderbehandlung programmieren zu müssen, indem ich den
operator,
überladen habe:namespace { template<typename result_type> any &operator,(any &lvalue, result_type &&rvalue) { return lvalue = rvalue; } template<typename result_type> any invoke(result_type (*fimpl)()) { any result; result = // Hier passiert, was mir nicht ganz geheuer ist: any(), fimpl(); return result; } long get_long() { return 42; } void get_void() { return; } } int main() { cout << "\tlong: " << invoke(&get_long).type().name() << endl << "\tvoid: " << invoke(&get_void).type().name() << endl; // cin.clear(); cin.get(); }
Für den Fall, dass auf der rechten Seite des
operator,
ein Ausdruck des Typsvoid
steht, findet die Zuweisung normal statt, gefolgt von der unabhängigen Ausführung des Ausdrucks. Ansonsten wird der zweite Ausdruck dem ersten zugewiesen. Gibt es noch einen weiteren Weg, denselben Effekt zu erreichen?Es existieren so viele Berührungsängste mit
void
, dass man den generellen Fall fast nicht beschreiben kann
-
Worauf willst du am Ende hinaus? Der Rückgabewert zählt sowieso nicht zur Funktionssignatur.
-
Zusammengefasst:
invoke
soll eine beliebige Funktion aufrufen und muss immer einany
zurückgeben. Wenn die aufgerufene Funktion den Rückgabetypvoid
hat, muss ein leeresany
zurückgegeben werden. Wie formuliere ichinvoke
so, dass es auch mitvoid
klar kommt?
-
Ich habe dafür immer zwei versionen von invoke gemacht.
Eine z.B. invokeWithReturn(..) und eine invokeWithoutReturn(..).Gefällt mir nicht sonderlich - bin also auch an einer eleganteren Lösung interessiert.
-
invoke<void>
spezialisieren?template<typename result_type> any invoke(result_type (*fimpl)()) { return fimpl(); } template<> any invoke<void>(void (*fimpl)()) { fimpl(); return any(); }
Oder übersehe ich etwas?
-
Ich denke jeder Compiler schluckt ein
void wrapper() { return returnsVoid(); }
MSVC tuts auf jeden Fall. Zumindestens bei templates.
-
ipsec schrieb:
invoke<void>
spezialisieren?template<typename result_type> any invoke(result_type (*fimpl)()) { return fimpl(); } template<> any invoke<void>(void (*fimpl)()) { fimpl(); return any(); }
Oder übersehe ich etwas?
Nein, so kann man es lösen. Allerdings wollte ich diese Lösung vermeiden, da es mit der Zeit sehr viele Spezialisierungen brauchen könnte, etwa so:
template<typename object_type> any invoke(any obj_r, void (object_type::*fimpl)()) { (any_cast<object_type *>(obj_r)->*fimpl)(); return any(); } template<typename object_type> any invoke(any obj_r, void (object_type::*fimpl)() const) { (any_cast<object_type *>(obj_r)->*fimpl)(); return any(); } template<typename object_type> any invoke(any obj_r, void (object_type::*fimpl)() volatile) { (any_cast<object_type *>(obj_r)->*fimpl)(); return any(); }
Jetzt stell dir vor, dass man noch weitere Signaturen unterstützen will. Für jede Variante, die noch
void
zurückgibt, kann man neu überladen.Ethon schrieb:
Ich denke jeder Compiler schluckt ein
void wrapper() { return returnsVoid(); }
MSVC tuts auf jeden Fall. Zumindestens bei templates.
Das ist klar, aber es soll ja für alle Fälle any returned werden
-
Kannst du dir nicht vielleicht was mit TypeTraits und enable_if zusammenbasteln?
-
Da brauchst du schon ein paar mehr Hilfsmittel, du musst ja aber das Rad nicht immer neu erfinden:
template<class Result, class Func> struct invoke_wrapper { static any call(Func&& f) { return f(); } } template<class Func> struct invoke_wrapper<void> { static any call(Func&& f) { f(); return any(); } } template<class Func> any invoke(Func&& f) { return invoke_wrapper<result_of<Func>::type, Func>::call(f); } template<typename result_type, typename object_type> any invoke(any obj_r, result_type (object_type::*fimpl)()) { return invoke(bind(fimpl, any_cast<object_type*>(obj_r))); }
-
Ich würds weiterleiten an ein template, das zwei Parameter hat, nämlich die Funktionssignatur und den Return-Typ:
template <class F, class Return_t = std::tr1::result_of<F>::type> struct generate_any { any operator()(F f) { return f(); } }; template <class F, void> struct generate_any { any operator()(F f) { f(); return any(); } } //Anwendung deiner Beispiele: template<typename result_type> any invoke(result_type (*fimpl)()) { generate_any<result_type(*)() /*, result_type*/> g; return g(fimpl); } template<typename object_type, typename return_type> any invoke(any obj_r, return_type (object_type::*fimpl)()) { generate_any<void (object_type::*)() /*, return_type*/> g; return g( std::tr1::bind(fimpl, any_cast<object_type*>(obj_r)) ); } //usw.
Man könnte mit C++0x jetzt noch weiter gehn:
template <class R, class... A, class... B> any invoke(std::function<R(A...)> f, B...Args);
Wenn man den Aufruf an ein Functor-Template weiterleitet, kann man darin bereits zur Compilezeit alles mögliche anstellen, vor allem die einzelnen Typen in B... überprüfen, wenns any's sind, beim Aufruf von f einen any_cast auf den passenden Typen in A... ausführen usw.