[C++0x] BaseTemplate<Args...> make_var(Args &&... in) - wie geht das?



  • Hallo,

    Die im Titel bereits angedeutete Funktion soll vor allem Schreibaufwand sparen, indem Klassen, die ihre Template-Typen für ihre Konstruktorargumente verwenden, einfacher erstellt werden können.

    Einfaches Beispiel:

    template<typename T1, typename T2> class Printer
    {
        public:
        Printer(T1 t1, T2 t2)
        {
            std::cout << t1 << " " << t2 << "\n";
        }
    };
    
    int main()
    {
        auto var = make_var<Printer>(5, 6);
    }
    

    Mein Versuch für make_var() sieht so aus:

    template<template<typename... Args> class BaseTemplate, typename... Args> BaseTemplate<Args...> make_var(Args &&... in)
    {
        return BaseTemplate<Args...>(in...);
    }
    
    error: no matching function for call to 'make_var(int, int)'
    error: unable to deduce 'auto' from '<expression error>'
    

    Auch wenn ich BaseTemplate direkt durch Printer ersetze, kommen Fehler:

    template<typename... Args> Printer<Args...> make_var(Args &&... in)
    {
        return Printer<Args...>(in...);
    }
    
    error: wrong number of template arguments (1, should be 2)
    error: provided for 'template<class T1, class T2> class Printer'
    

    Weiß jemand, wie man make_var implementieren kann?

    mfg
    wxSkip



  • Ich würde es so (ungetestet) machen:

    template
    <
        template <typename...> class T,
        typename... Args
    >
    T<Args...> make_var(Args&&... args)
    {
        return T<Args...>(std::forward<Args>(args)...);
    }
    


  • 314159265358979 schrieb:

    Ich würde es so (ungetestet) machen:

    template
    <
        template <typename...> class T,
        typename... Args
    >
    T<Args...> make_var(Args&&... args)
    {
        return T<Args...>(std::forward<Args>(args)...);
    }
    

    Genau der gleiche Fehler, denn außer dem std::forward und dem nicht-Angeben von Args bei class T hast du ja auch nichts geändert.


  • Mod

    Das ist ein Fall von GCC Bug #35722
    Als workaround (dort zu finden) geht:

    #include <iostream>
    #include <utility>
    
    template<typename T1, typename T2>
    class Printer
    {
    public:
        Printer(T1 t1, T2 t2)
        {
            std::cout << t1 << " " << t2 << "\n";
        }
    };
    
    template<template <typename...> class T, typename... Args>
    struct Join
    {
        typedef T<Args...> type;
    };
    
    template <template <typename...> class T, typename... Args>
    typename Join<T, Args...>::type make_var(Args&&... args)
    {
        return typename Join<T, Args...>::type(std::forward<Args>(args)...);
    }
    
    int main()
    {
        auto var = make_var<Printer>(5, 6);
    }
    


  • Man darf nicht vergessen, dass beim Muster Arg&& wobei Arg deduziert wird, für Arg ein Lvalue-Referenz-Typ rauskommen kann.

    Wenn man bei Lvalues kein Printer<int&,int&>-Objekt bekommen möchte und bei konstanten Ausdrücken das const stört, sollte man folgendes schreiben:

    Join<T, typename std::decay<Args>::type ... >::type
    

    So ähnlich arbeitet zB auch make_tuple. Also,

    int i = 3;
    auto x = make_tuple(i,3.1415);
    

    macht aus x ein tuple<int,double> und kein tuple<int&,double> obwohl der entsprechende Templateparameter des make_tuple-Templates als int& deduziert wird; denn i ist ein Lvalue. Die Sonderbehandlung bei make_tuple bzgl reference_wrapper<> verschweige ich jetzt mal.



  • Danke camper und krümelkracker!
    @krümelkracker: Wusste gar nicht, dass man T & && schreiben kann... Ich habe extra die rvalue-Referenz hingesetzt, um lvalue-Referenzen zu vermeiden.
    @camper: Ich hatte sogar noch einen GCC-Bug mit variadic templates im Hinterkopf - wusste aber nicht mehr genau, worum es dabei ging.



  • Könnte man jetzt auch noch make_var<Printer, true, false, 5>(1, 7) schreiben, was dann einen Printer<true, false, 5, int, int>(1,7) zurückgibt?
    template<typename... Types> Types... template_values funktioniert schon einmal nicht.



  • wxSkip schrieb:

    @krümelkracker: Wusste gar nicht, dass man T & && schreiben kann...

    Kann man auch nicht -- also & und && direkt hintereinander im Quelltext. 🙂
    Aber man kann T && schreiben, auch wenn T=int& gilt. Der Effekt: T&&=int& ("reference collapsing"). Ist ein & dabei, "drückt sich das durch". Sonst bleibt's &&.

    wxSkip schrieb:

    Ich habe extra die rvalue-Referenz hingesetzt, um lvalue-Referenzen zu vermeiden.

    Dann bist Du -- so wie viele andere -- in die komische-Perfect-Forwarding-erlaubende-Deduktionsregel-Falle gelaufen; denn T&& fängt alles, falls T deduziert wird -- sowohl Rvalues als auch Lvalues.

    template<class T> void demo(T&&) {}
    int main() {
      int i = 42;
      demo(i); // --> T = int&, T&& = int&
      demo(6); // --> T = int , T&& = int&&
    }
    


  • wxSkip schrieb:

    Könnte man jetzt auch noch make_var<Printer, true, false, 5>(1, 7) schreiben, was dann einen Printer<true, false, 5, int, int>(1,7) zurückgibt?
    template<typename... Types> Types... template_values funktioniert schon einmal nicht.

    Du müsstest für jede Anzahl an Compiletime-Argumenten eine Überladung schreiben, da man nicht mehr als 1 typename... in einem template haben kann.



  • 314159265358979 schrieb:

    wxSkip schrieb:

    Könnte man jetzt auch noch make_var<Printer, true, false, 5>(1, 7) schreiben, was dann einen Printer<true, false, 5, int, int>(1,7) zurückgibt?
    template<typename... Types> Types... template_values funktioniert schon einmal nicht.

    Du müsstest für jede Anzahl an Compiletime-Argumenten eine Überladung schreiben, da man nicht mehr als 1 typename... in einem template haben kann.

    Könnte man dann eine Ersatzlösung wie make_var<Printer, ValueHolder<true, false, 5> >(1, 7) verwenden? Konkret geht es mir um die Implementierung von ValueHolder bzw. wie kann ich es vermeiden TypeHolder<bool, bool, int>::ValueHolder<true, false, 5> schreiben zu müssen?

    EDIT: Einfacher wäre wohl ValueHolder<true, false, 5>::make_var<Printer>(1, 7) . Das ändert allerdings nichts am Problem der impliziten Typerkennung bei Template-Wert-Argumenten.



  • Ich muss mich selbst korrigieren, nicht mal mit Überladung ist das lösbar. Zu deinem Edit: Muss ich mal nachdenken und evtl das heilige IRC befragen.



  • 314159265358979 schrieb:

    Ich muss mich selbst korrigieren, nicht mal mit Überladung ist das lösbar. Zu deinem Edit: Muss ich mal nachdenken und evtl das heilige IRC befragen.

    Was ist denn das IRC? Wenn ich danach suche, finde ich bloß den hiesigen Foren-Channel...



  • Internet Relay Chat 😉



  • Das zweite ist auch nicht möglich, aus dem selben Grund wie das erste - Du kannst kein template machen, das beliebige Compiletime-Werte schluckt.



  • 314159265358979 schrieb:

    Das zweite ist auch nicht möglich, aus dem selben Grund wie das erste - Du kannst kein template machen, das beliebige Compiletime-Werte schluckt.

    Ich habe schon versucht, den zusätzlichen Schreibaufwand bei der TypeHolder-Variante durch ein Makro mit decltype() und einer Funktion, die den TypeHolder-Typ zurückliefert, zu umgehen, aber dann kam ich auf Fehler bei Join bei typedef T<values..., Args...> type; (der Bug) und template<ValueTypes..., typename...> class T (anderer Fehler), sodass ich es jetzt mit einer für mich verwendbaren Alternative (die IMHO beim Aufruf auch schöner aussieht) versuche:

    template<bool b1, int i1> class Holder
    {
        public:
        template<typename T1, typename T2> class Printer
        {
            public:
            Printer(T1 t1, T2 t2)
            {
                std::cout << b1 << " " << i1 << " " << t1 << " " << t2 << "\n";
            }
        };
    };
    
    template<template<typename...> class T, typename... Args>
    struct Join
    {
        typedef T<Args...> type;
    };
    
    template <template<typename...> class T, typename... Args>
    static typename Join<T, typename std::decay<Args>::type...>::type
        make_var(Args&&... args)
    {
        return typename Join<T, typename std::decay<Args>::type...>::type(std::forward<Args>(args)...);
    }
    
    int main()
    {
        auto var = make_var<Holder<true, 5>::Printer>(5, 6);
    }
    


  • Vielleicht wäre ja das hier etwas für dich:

    template <bool b, int i, typename T0, typename T1>
    struct printer
    {
        printer(T0 t0, T1 t1)
        {
            std::cout << t0 << b << t1 << i;
        }
    };
    
    template
    <
        template <bool, int, typename...> class T,
        bool B,
        int I,
        typename... Args
    >
    struct join
    {
        typedef T<B, I, Args...> type;
    };
    
    template <bool B, int I, typename... Args>
    typename join<printer, B, I, typename std::decay<Args>::type...>::type make_printer(Args&&... args)
    {
        return typename join<printer, B, I, typename std::decay<Args>::type...>::type(std::forward<Args>(args)...);
    }
    

    Der Aufruf sieht dann einfach so aus:

    make_printer<true, 42>(3.14, 666);
    

    Hier sind allerdings die ersten beiden Template-Parameter immer bool und int.



  • 314159265358979 schrieb:

    Vielleicht wäre ja das hier etwas für dich:

    template <bool b, int i, typename T0, typename T1>
    struct printer
    {
        printer(T0 t0, T1 t1)
        {
            std::cout << t0 << b << t1 << i;
        }
    };
     
    template
    <
        template <bool, int, typename...> class T,
        bool B,
        int I,
        typename... Args
    >
    struct join
    {
        typedef T<B, I, Args...> type;
    };
     
    template <bool B, int I, typename... Args>
    typename join<printer, B, I, typename std::decay<Args>::type...>::type make_printer(Args&&... args)
    {
        return typename join<printer, B, I, typename std::decay<Args>::type...>::type(std::forward<Args>(args)...);
    }
    

    Der Aufruf sieht dann einfach so aus:

    make_printer<true, 42>(3.14, 666);
    

    Hier sind allerdings die ersten beiden Template-Parameter immer bool und int.

    Danke, aber ich brauche es dynamisch (sowohl für das Basis-Template als auch für die Werte).



  • Muss denn die Anzahl an möglichen Compile-Time Argumenten auch variabel sein oder sind es immer nur 2?


  • Mod

    wxSkip schrieb:

    Danke, aber ich brauche es dynamisch (sowohl für das Basis-Template als auch für die Werte).

    Wie dynamisch? Wenn B und I nicht beim Compilieren bekannt sind, können sie keine Templateparameter sein.



  • camper schrieb:

    wxSkip schrieb:

    Danke, aber ich brauche es dynamisch (sowohl für das Basis-Template als auch für die Werte).

    Wie dynamisch? Wenn B und I nicht beim Compilieren bekannt sind, können sie keine Templateparameter sein.

    Ich meinte, die Anzahl und die Typen sollen dynamisch, also Compiletime-Abhängig und nicht hardgecoded sein.


Anmelden zum Antworten