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 von invoke und bind 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 Variante invoke<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 Typs void 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 😉


  • Mod

    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 ein any zurückgeben. Wenn die aufgerufene Funktion den Rückgabetyp void hat, muss ein leeres any zurückgegeben werden. Wie formuliere ich invoke so, dass es auch mit void 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.


Log in to reply