Template-Parameter: Übergabe per Wert oder Referenz?



  • Tag zusammen,

    folgendes Problem: Ich habe ein Klassentemplate foo<T>, das von T ableitet. Nehmen wir an, alle Typen, die ich für T einsetze, hätten einen Konstruktor mit einem Parameter. Dann möchte ich foo<T> mit allen Parametern konstruieren können, mit denen ich auch T konstruieren könnte.

    Bei einigen Typen müssen die Parameter aber per Wert übergeben werden, bei anderen hingegen per Referenz. Ein bißchen Code sagt mehr als tausend Worte:

    #include <boost/noncopyable.hpp>
    
    template <typename T>
    class foo
      : public T
    {
      public:
        template <typename A>
        foo(A a) : T(a) { }       // funktioniert für int_holder
        //foo(A & a) : T(a) { }   // funktioniert für blah_holder
    };
    
    class int_holder
    {
      public:
        int_holder(int n) : _n(n) { }
      private:
        int _n;
    };
    
    class blah : boost::noncopyable { };
    
    class blah_holder
    {
      public:
        blah_holder(blah & b) : _b(b) { }
      private:
        blah & _b;
    };
    
    int main()
    {
        foo<int_holder> x(42);
    
        blah b;
        foo<blah_holder> y(b);
    }
    

    Die erste Variante des Konstruktors von foo funktioniert für T = int_holder. Mit T = blah_holder kann die Übergabe per Wert aber nicht gehen, da blah nicht kopierbar ist.
    Nehme ich die 2. (auskommentierte) Variante des Konstruktors, dann funktioniert's zwar mit blah_holder, aber mit int_holder schlägt's fehl, da ich ja eine Referenz auf ein Literal übergeben würde.

    Wie kann ich dieses Problem lösen, so daß foo einen Konstruktor hat, der in beiden Fällen funktioniert? Eigentlich möchte ich ja nichts weiter, als daß alle Parameter einfach nur unverändert an den Konstruktor von T "weitergegeben" werden.

    Danke schonmal für alle sachdienlichen Hinweise...


  • Administrator

    template <typename T>
    class foo
    	: public T
    {
    public:
    	template <typename A>
    	foo(A& a) : T(a) { }
    
    	template <typename A>
    	foo(A const& a) : T(a) { }
    };
    

    Grüssli



  • Dann möchte ich foo<T> mit allen Parametern konstruieren können, mit denen ich auch T konstruieren könnte.

    Das nennt sich das "forwarding problem", und es gibt in C++ keine (praktikable) Lösung.

    Die Lösung von Dravere funktioniert, ist aber nicht praktisch wenn man mehrere Parameter übergeben möchte, da man 2^N Overloads bräuchte (N=Anzahl der Parameter).

    http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2002/n1385.htm
    (siehe Absatz "Current Solutions")



  • hustbaer schrieb:

    Das nennt sich das "forwarding problem", und es gibt in C++ keine (praktikable) Lösung.

    Verdammt, C++ kann manchmal ein richtiger Spielverderber sein...

    hustbaer schrieb:

    Die Lösung von Dravere funktioniert, ist aber nicht praktisch wenn man mehrere Parameter übergeben möchte, da man 2^N Overloads bräuchte (N=Anzahl der Parameter).

    Hmm... was ich bräuchte wäre tatsächlich eine allgemeine Lösung für eine beliebige Zahl von Parametern (in gewissen sinnvollen Grenzen, versteht sich). Ich frage mich, ob sich da nicht mit Boost.Preprocessor was basteln ließe... Hat das schonmal jemand versucht? Das wäre zwar sicher keine schöne Lösung, aber mangels Alternativen...



  • hustbaer schrieb:

    Das nennt sich das "forwarding problem", und es gibt in C++ keine (praktikable) Lösung.

    sicher gibt es praktikable lösungen - nur ideale gibts nicht.

    foo<int_holder> x(int_holder(42));
    
        blah b;
        foo<blah_holder> y(blah_holder(b));
    

  • Administrator

    Meine Lösung bezog sich auch nur auf das Beispiel, also für einen Parameter. Für variable Parameter entweder die Lösung von Shade Of Mine nehmen, mehrfach Überladungen machen, wie es in Boost der Fall ist (Boost.Preprocessor könnte hilfreich sein) oder auf C++0x warten. Variadic Templates sollten das Problem wohl lösen.

    template <typename T>
    class foo
        : public T
    {
    public:
        template <typename ...A>
        foo(A& ...a) : T(a...) { }
    
        template <typename A>
        foo(A const& ...a) : T(a...) { }
    };
    

    Grüssli



  • Shade Of Mine schrieb:

    sicher gibt es praktikable lösungen - nur ideale gibts nicht.

    foo<int_holder> x(int_holder(42));
    
    blah b;
    foo<blah_holder> y(blah_holder(b));
    

    Der Ansatz funktioniert bei mir leider nicht, da einige der in foo enthaltenen Klassen nicht kopierbar sind. Ich muß also schon direkt das foo<T> konstruieren können 😞

    Dravere schrieb:

    Variadic Templates sollten das Problem wohl lösen.

    Aber auch erst in Verbindung mit Rvalue-References, oder? Also in etwa so:

    template <typename ...A>
    foo(A && ...a) : T(a...) { }
    

    Wenn der neue Standard nur endlich mal fertig würde...

    Boost.Preprocessor habe ich schon einige Male erfolgreich benutzt, um damit quasi Variadic Templates zu simulieren. Aber wie kann ich mir denn alle möglichen Kombinationen von "A & a" und "A const & a" erzeugen lassen?


  • Administrator

    dooooomi schrieb:

    Aber wie kann ich mir denn alle möglichen Kombinationen von "A & a" und "A const & a" erzeugen lassen?

    Ehm, moment, willst du auch Kreuzkombinationen zulassen? Also zum Beispiel sowas:

    template <typename T>
    class foo
        : public T
    {
    public:
        template <typename A, typename B>
        foo(A& a, B const& b) : T(a...) { }
    
        template <typename A, typename B>
        foo(A const& a, B& b) : T(a...) { }
    };
    

    Ich dachte bisher, dass alle Werte per Value oder alle Werte per Referenz weitergegeben würden. Aber so ist das natürlich ... ufff
    Das gibt eine Menge an Kombinationsmöglichkeiten 😃

    Kannst du das nicht ein wenig einschränken?
    Ansonsten, wie wäre es mit einem Passthrough Pattern oder wie man dem Ding sagt *lol*

    template<typename T>
    class foo
        : public T
    {
    public:
        foo(typename T::ConstrutorParams const& params) : T(params) { }
    };
    

    Jeder Typ hat somit eine Struktur, welche die Parameter für den Konstruktor aufnimmt.

    Grüssli



  • Dravere schrieb:

    Aber so ist das natürlich ... ufff
    Das gibt eine Menge an Kombinationsmöglichkeiten 😃

    So ist es... 🙄

    Einschränken kann ich das nicht wirklich, da das ganze mit beliebigen Typen für T funktionieren soll. Auch die "Passthrough" Variante fällt somit leider weg, da ich dafür ja die jeweiligen Klassen um einen zusätzlichen Konstruktor erweitern müßte.

    Boost.Preprocessor wäre für mich eine akzeptable Lösung, solange sich dadurch die Compile-Zeiten nicht allzu extrem verlängern. Und wenn ich beispielsweise für bis zu 10 Parameter entsprechende Konstruktoren erzeugen würde, dann wären das ja auch "nur" 2047 Kombinationen 🙂

    Aber wie gesagt, mit Boost.Preprocessor weiß ich derzeit nicht wirklich weiter...


  • Administrator

    Ok, dann krame ich halt noch die letzte Möglichkeit aus. Diese in Kombination mit Boost.Preprocessor dürfte wohl fast am einfachsten sein:

    template<typename T>
    class Foo
        : public T
    {
    public:
        template<typename T1>
        Foo(T1 p1) : T(p1) { }
    
        template<typename T1, typename T2>
        Foo(T1 p1, T2 p2) : T(p1, p2) { }
    
        // usw. mit Boost.Preprocessor oder von Hand, wie du willst.
    };
    
    // Verwendung:
    int a = 0;
    int b = 0;
    
    Foo<XYZ> f1(a, b); // Alle per Value
    Foo<XYZ> f2(boost::ref(a), b); // Erste per Referenz, zweite per Value
    Foo<XYZ> f3(boost::ref(b)); // Einmal per Referenz
    

    Usw., wie du es halt gerne möchtest. Es wird also alles per Value übergeben und eine Wrapperklasse für Referenzen genutzt, welche implizit in eine "normale" Referenz umgewandelt werden kann. Boost.Bind macht dies, glaub ich, auch so.

    Grüssli



  • Dravere schrieb:

    Ok, dann krame ich halt noch die letzte Möglichkeit aus. Diese in Kombination mit Boost.Preprocessor dürfte wohl fast am einfachsten sein:

    ...
    
    Foo<XYZ> f1(a, b); // Alle per Value
    Foo<XYZ> f2(boost::ref(a), b); // Erste per Referenz, zweite per Value
    Foo<XYZ> f3(boost::ref(b)); // Einmal per Referenz
    

    Usw., wie du es halt gerne möchtest. Es wird also alles per Value übergeben und eine Wrapperklasse für Referenzen genutzt, welche implizit in eine "normale" Referenz umgewandelt werden kann. Boost.Bind macht dies, glaub ich, auch so.

    Danke, die Lösung finde ich gar nicht schlecht. Daß der Aufrufer des Konstruktors in diesem Fall wissen muß, wann boost::ref nötig ist, damit kann ich leben.

    Wenn natürlich noch jemand eine Idee hätte, wie man sämtliche nötigen Overloads (mit konstanten und nicht-konstanten Referenzen) per Boost.Preprocessor erzeugen könnte, würde mich das nach wie vor sehr interessieren.



  • Shade Of Mine schrieb:

    hustbaer schrieb:

    Das nennt sich das "forwarding problem", und es gibt in C++ keine (praktikable) Lösung.

    sicher gibt es praktikable lösungen - nur ideale gibts nicht.

    Lösung des "forwarding problem" impliziert für mich vollständig/ideal. Das ist natürlich Definitionssache, aber so habe ich es gemeint.

    Und die einzige (vollständige) Lösung die es gibt, ist eben nicht praktikabel.

    So war das zu verstehen.

    Natürlich gibt es mehrere Möglichkeiten es irgendwie zu machen, die sind in dem von mir verlinkten Paper eh alle angeführt (zumindest alle relevanten) - inklusive Vor- und Nachteile.



  • dooooomi schrieb:

    Wenn natürlich noch jemand eine Idee hätte, wie man sämtliche nötigen Overloads (mit konstanten und nicht-konstanten Referenzen) per Boost.Preprocessor erzeugen könnte, würde mich das nach wie vor sehr interessieren.

    Das würde ich garnicht erst versuchen. Das würde dir die Compile-Zeiten extrem hochjagen. Angenommen du willst z.B. 10 Parameter zulassen, wären das schon 1024 Overloads. Bei jedem Aufruf müsste der Compiler dann "das beste" aus 1024 Funktionstemplates aussuchen.

    Wenn ihr gratis Kaffee in der Firma habt, und du gerne erzwungene Pausen machst, dann mach es so 😃



  • hustbaer schrieb:

    dooooomi schrieb:

    Wenn natürlich noch jemand eine Idee hätte, wie man sämtliche nötigen Overloads (mit konstanten und nicht-konstanten Referenzen) per Boost.Preprocessor erzeugen könnte, würde mich das nach wie vor sehr interessieren.

    Das würde ich garnicht erst versuchen. Das würde dir die Compile-Zeiten extrem hochjagen. Angenommen du willst z.B. 10 Parameter zulassen, wären das schon 1024 Overloads. Bei jedem Aufruf müsste der Compiler dann "das beste" aus 1024 Funktionstemplates aussuchen.

    Wenn ihr gratis Kaffee in der Firma habt, und du gerne erzwungene Pausen machst, dann mach es so 😃

    Leider hast du völlig Recht. Ich habe mir einmal die Overloads für 10 Parameter erzeugt (nicht mit Boost.Preprocessor, sondern mittels eines kleinen Python-Scripts). Da hat der Compiler schon kräftig dran zu arbeiten. Und wenn ich mir dann vorstelle, wie das erst sein soll, wenn der Code nicht "fertig" generiert wäre, sondern erst vom Präprozessor erzeugt würde, der ja auch nie für sowas gedacht war...

    Mit einer kleineren Anzahl von Parametern (konkret brauche ich derzeit maximal 7) wird's natürlich deutlich schneller, aber eine wirkliche Lösung ist das ja auch nicht. Dann bleibe ich lieber bis auf Weiteres bei Dravere's letztem Vorschlag, und freue mich schonmal auf C++0x, wo das alles dank Variadic Templates und Rvalue-References dann in einer einzigen Zeile geht 🙂


Anmelden zum Antworten