TMP: Permutationen von Template-Parametern generieren



  • Hallo zusammen,

    ich überlege gerade, ob man meine Factory-Funktion MakeBar mittels TMP oder sonstwie eleganter hinbekommen könnte, habe aber noch überhaupt keinen Ansatz. Etwas mit Andreischen typelists vielleicht?

    #include <memory>
    
    struct Foo1 {};
    struct Foo2 {};
    struct Foo3 {};
    
    struct BarBase {};
    
    template<typename T1, typename T2>
    struct Bar : public BarBase {};
    
    typedef std::unique_ptr<BarBase> BarPtr;
    
    BarPtr MakeBar( int id1, int id2 ) {
    	if ( id1 == 1 && id2 == 1 )
    		return BarPtr( new( Bar<Foo1, Foo1> ) );
    	else if ( id1 == 1 && id2 == 2 )
    		return BarPtr( new( Bar<Foo1, Foo2> ) );
    	else if ( id1 == 1 && id2 == 3 )
    		return BarPtr( new( Bar<Foo1, Foo3> ) );
    	else if ( id1 == 2 && id2 == 1 )
    		return BarPtr( new( Bar<Foo2, Foo1> ) );
    	else if ( id1 == 2 && id2 == 2 )
    		return BarPtr( new( Bar<Foo2, Foo2> ) );
    	else if ( id1 == 2 && id2 == 3 )
    		return BarPtr( new( Bar<Foo2, Foo3> ) );
    	else if ( id1 == 3 && id2 == 1 )
    		return BarPtr( new( Bar<Foo3, Foo1> ) );
    	else if ( id1 == 3 && id2 == 2 )
    		return BarPtr( new( Bar<Foo3, Foo2> ) );
    	else if ( id1 == 3 && id2 == 3 )
    		return BarPtr( new( Bar<Foo3, Foo3> ) );
    	return BarPtr();
    }
    
    int main() {
    	int userInput1( 2 );
    	int userInput2( 3 );
    	BarPtr ptr( MakeBar( userInput1, userInput2 ) );
    }
    

    Irgendwelche Ideen, wie man das viele Instanziieren des Klassentemplates nicht mehr manuell machen muss?

    Gruß
    Dobi


  • Mod

    Das geht, ich habe allerdings gerade keinen Compiler zur Hand, um eine fertige Lösung zu erstellen (außerdem ist das eine der Aufgaben für PI :p)
    Kurz skizziert könnte das ungefähr so aussehen (wobei mir diese Lösung nihct besonders gut gefällt), da ohne Compiler komplett ungetest und wahrscheinlich auch mit einigen Syntaxfehlern:

    template <typename... T>
    struct typelist : std::identity<typelist<T...>> {};
    
    template <typename... L>
    struct concat_typelists : std::identity<concat_typelists<typename L::type>> {};
    
    template <typename... T, typename... U, typename... L>
    struct concat_typelists<typelist<T...>, typelist<U...>, L...> : concat_typelists<typelist<T..., U...>, L...> {};
    
    template <std::size_t... N, typename L>
    struct repeat_list;
    
    template <std::size_t... N, typename... T>
    struct repeat_list<N, typelist<T...>> : concat_typelists<typelist<T...>, repeat_list<N-1, T...>> {};
    
    template <typename... T>
    struct repeat_list<0, typelist<T...>> : typelist<> {};
    
    template <std::size_t... N, typename L>
    struct repeat_types;
    
    template <std::size_t... N, typename... T>
    struct repeat_types<N, typelist<T...>> : concat_typelists<repeat_list<N, T>...> {};
    
    template <typename T, typename U>
    void foo();
    
    template <typename... T, typename... U>
    void dispatch_helper(std::size_t i, typelist<T...>, typelist<U...>)
    {
        static const void (*foos[])() = { foo<T, U>... };
        return foos[i]();
    }
    
    template <typename... T>
    void dispatch(std::size_t i, std::size_t j)
    {
        using types = typelist<T...>;
        using types_repeated = repeat_types<sizeof...(T), types>;
        using list_repeated = repeat_list<sizeof...(T), types>;
        dispatch_helper(i*sizeof...(T)+j, types_repeated(), list_repeated());
    };
    


  • template <typename... T>
    struct typelist : std::identity<typelist<T...>> {};
    

    Wie funktioniert das ? Warum erbt camper hier von std::identity ??



  • Das ist ein TMP-Abkürzungs-Trick. std::identity ist äquivalent zu:

    template <typename T>
    struct identity
    {
        typedef T type;
    };
    

    Da viele TMP-Algorithmen ihr Ergebnis durch ein typedef type herausgeben, lässt sich ein Algorithmus xyz

    template <typename Foo>
    struct xyz
    {
        typedef Foo type;
    };
    

    kürzer schreiben als

    template <typename Foo>
    struct xyz
        : identity<Foo>
    {};
    

    camper lässt nun seine typelist selbst von std::identity erben. Wenn er nun als Ergebnis eine Leere Typliste haben möchte, kann er deshalb nochmal abkürzen und statt

    : identity<typelist<>>
    

    direkt

    : typelist<>
    

    schreiben. Sein repeat_list macht davon Gebrauch.



  • Pseudo-Edit: Das "Leere" aus dem letzten Satz streichen. Geht natürlich mit allen Typlisten.



  • Ok, danke. Ich wusste vorher noch gar nicht, das man mehr als ein Variadic Template Argument haben kann. 👍



  • Das geht auch nur bei Klassentemplate-Spezialisierungen und Funktionstemplates wo die Parameter deduziert werden können.



  • 314159265358979__ schrieb:

    Das geht auch nur bei Klassentemplate-Spezialisierungen und Funktionstemplates wo die Parameter deduziert werden können.

    Ja, das habe ich mir auch schon gedacht. Sonst wäre das ja nicht mehr eindeutig.



  • Wow, super, vielen Dank. Jetzt mach ich mich mal ans Verstehen. 😉



  • 314159265358979__ schrieb:

    Wenn er nun als Ergebnis eine Leere Typliste haben möchte, kann er deshalb nochmal abkürzen und statt

    : identity<typelist<>>
    

    direkt

    : typelist<>
    

    schreiben. Sein repeat_list macht davon Gebrauch.

    Wenn ich das jetzt richtig verstehe, heißt das, dass eine Klasse von sich selbst ableiten kann? o.O


  • Mod

    Hier die bereinigte Version.

    #include <iostream>
    #include <string>
    #include <typeinfo>
    
    template <typename T, typename U>
    void foo()
    {
        std::cout << typeid(T).name() << '\t' << typeid(U).name() << '\n';
    }
    
    template <typename T> struct identity { using type = T; };
    
    template <typename... T>
    struct typelist : identity<typelist<T...>> {};
    
    template <typename... L>
    struct concat_typelists : concat_typelists<typename L::type...> {};
    
    template <typename... T, typename... U, typename... L>
    struct concat_typelists<typelist<T...>, typelist<U...>, L...> : concat_typelists<typelist<T..., U...>, L...> {};
    
    template <typename... T>
    struct concat_typelists<typelist<T...>> : typelist<T...> {};
    
    template <std::size_t N, typename L>
    struct repeat_list;
    
    template <std::size_t N, typename... T>
    struct repeat_list<N, typelist<T...>> : concat_typelists<typelist<T...>, repeat_list<N-1, typelist<T...>>> {};
    
    template <typename... T>
    struct repeat_list<0, typelist<T...>> : typelist<> {};
    
    template <std::size_t N, typename L>
    struct repeat_types : repeat_types<N, typename L::type> {};
    
    template <std::size_t N, typename... T>
    struct repeat_types<N, typelist<T...>> : concat_typelists<repeat_list<N, typelist<T>>...> {};
    
    template <typename... T, typename... U>
    void dispatch_helper(std::size_t i, typelist<T...>, typelist<U...>)
    {
        static void (*const foos[])() = { foo<T, U>... };
        return foos[i]();
    }
    
    template <typename... T>
    void dispatch(std::size_t i, std::size_t j)
    {
        using types = typelist<T...>;
        using types_repeated = repeat_types<sizeof...(T), types>;
        using list_repeated = repeat_list<sizeof...(T), types>;
        dispatch_helper(i*sizeof...(T)+j, types_repeated(), list_repeated());
    };
    
    struct Foo {};
    struct Bar {};
    struct Baz {};
    
    int main(int argc, char** argv)
    {
        if ( argc >= 2 )
            dispatch<Foo,Bar,Baz>(std::stoul(argv[1]), std::stoul(argv[2]));
    }
    

    TMP-Code leidet häufig darunter, dass eine Menge syntaktischer Lärm erzeugt wird. Die Auswertung von Metafunktionen erfolgt üblicherweise durch die Verwendung des in der Funktion enthaltenen typedefs type. Innerhalb von templates führt das schnell zu typename Foo::type-Orgien.
    Unter Beachtung zweier Regeln lässt sich das weitgehend vermeiden: die typenames ...::type stehen zwar noch da, aber nicht mehr an einer interessanten Stelle; sondern in der Definition des Primärtemplates, das andernfalls undefiniert bliebe.
    1. Jedes Template, das eigentlich keine Funktion darstellt, erbt von identity, enhält also ein typedef auf sich selbst und ist damit eine Metafunktion, die sich selbst zurückgibt.
    2. Jede andere Metafunktion erbt von ihrem Ergebnis.

    Auf diese Weise kann dann auch typename ::type an Stellen wie in Zeilen 50-53 vermieden werden. Der Funktionsaufruf funktioniert, weil hierfür eine besondere (und relativ selten gebrauchte) Deduktionsregel existiert, die es erlaubt, eine Basisklasse zu deduzieren, wenn die Deduktion andernfalls fehlschlagen würde.

    std::identity ist leider wieder aus dem Standard entfernt worden 😞



  • Hacker schrieb:

    Wenn ich das jetzt richtig verstehe, heißt das, dass eine Klasse von sich selbst ableiten kann? o.O

    Nein. Aber du darfst eine Klasse als Template-Parameter der Basisklasse verwenden. Das nennt man dann CRTP.



  • 314159265358979__ schrieb:

    Hacker schrieb:

    Wenn ich das jetzt richtig verstehe, heißt das, dass eine Klasse von sich selbst ableiten kann? o.O

    Nein. Aber du darfst eine Klasse als Template-Parameter der Basisklasse verwenden. Das nennt man dann CRTP.

    Das kenn ich schon längst, aner hier meintest du doch es wär die vereinfachte Version.. die ohne CRTP.

    Edit:

    template <typename... T>
    struct typelist : std::identity<typelist<T...>> {};
    

    Das geht natürlich. Das ist CRTP. Das..

    template <typename... T>
    struct typelist : typelist<>{};
    

    Geht doch nicht? Obwohl es natürlich verschiedene Klassen sind.. der Konstruktor bswp. bleibt gleich, oder? Und auch der Compiler spuckt entsprechende Fehlermeldungen aus (na nicht die, die ich grade meine, aber z. B. dass typelist unvollständig ist)...


Anmelden zum Antworten