C++0x variadic templates Elemente zur Runtime



  • Hi,

    ich habe eine Template-Klasse mit variierenden Argumenten. Sie nimmt eine Zahl von unsigned ints an. Die Einzelnen Elemente zur Compilezeit zu bekommen geht:

    template <unsigned int I>
        struct Index {
            enum { N = I };
        };
    
        template <class I, unsigned int... Indicies_>
        struct IndexEntry;
    
        template <unsigned int First, unsigned int... Indicies_>
        struct IndexEntry<Index<0>, First, Indicies_...> {
            enum { N = First };
        };
    
        template <class I, unsigned int First, unsigned int... Indicies>
        struct IndexEntry<I, First, Indicies...> {
            enum { N = IndexEntry<Index<I::N - 1>, Indicies...>::N };
        };
    
        template <unsigned int... Indicies>
        struct View {
    
            enum { NIndicies = sizeof...(Indicies) };
    
            template <unsigned int I>
            struct IndexAt {
                enum { Result = IndexEntry<VecPrivate_::Index<I>, Indicies...>::N };
            };
    
            template <typename T1>
            unsigned int IndexAtI(T1 i) {
                // Wie soll das gehen?
            }
        };
    

    Aber wie soll das zur Runtime gehen?!

    -- Danke


  • Mod

    Templates und Runtime passt allgemein nicht zusammen, egal ob variadisch oder nicht.



  • Also muss ich doch noch alles durch ein switch-Statement "wrappen" oder gibts da nicht doch noch eine andere Möglichkeit? Man kann ja zur Runtime auf Template-Argumente zugreifen, warum soll das hier nicht gehen?


  • Mod

    IchWillsZurRuntime schrieb:

    Man kann ja zur Runtime auf Template-Argumente zugreifen, warum soll das hier nicht gehen?

    Weil alle(!) Templates zur Compilezeit ausgewertet werden. Was genau meinst du mit "zur Laufzeit zugreifen"? Du kannst zur Laufzeit keine Templates instanzieren, du kannst aber natürlich benutzen was zur Compilezeit instanziert wurde.

    Also muss ich doch noch alles durch ein switch-Statement "wrappen" oder gibts da nicht doch noch eine andere Möglichkeit?

    Dazu müsstes du erklären, was du überhaupt erreichen möchtest. (Und antworte nicht, dass du variadische Templates zur Laufzeit auswerten möchtest, ich meine das was dein Programm machen soll von dem du glaubst, diese Technik zu benötigen)

    edit: Ach, jetzt verstehe ich, was du möchtest. 💡 Tut mir leid, ich habe vorher falsch verstanden was du wolltest. Hmm, ja, das sollte sich machen lassen, aber ist gerade ein bisschen spät und die Antwort nicht ganz leicht.



  • Die View Klasse soll später bei ihrer Vektor-Union auf die Daten zugreifen. Dazu soll der Index-Operator und nicht nur die get-Methoden, die Indizes als Template-Argumente benötigen, implementiert werden.

    Hier mal mehr Code:

    template <unsigned int I>
        struct Index {
            enum { N = I };
        };
    
        template <class I, unsigned int... Indicies_>
        struct IndexEntry;
    
        template <unsigned int First, unsigned int... Indicies_>
        struct IndexEntry<Index<0>, First, Indicies_...> {
            enum { N = First };
        };
    
        template <class I, unsigned int First, unsigned int... Indicies>
        struct IndexEntry<I, First, Indicies...> {
            enum { N = IndexEntry<Index<I::N - 1>, Indicies...>::N };
        };
    
        template <unsigned int N_, typename T_, unsigned int... Indicies>
        struct View {
    
            enum { NIndicies = sizeof...(Indicies) };
    
            template <unsigned int I>
            struct IndexAt {
                enum { Result = IndexEntry<VecPrivate_::Index<I>, Indicies...>::N };
            };
    
            template <typename T1>
            unsigned int IndexAtI(T1 i) {
                    // ???
            }
    
            template <unsigned int I>
            T_& get() {
                return (*reinterpret_cast<Vec<N_, T_>*>(this)).get<IndexAt<I>::Result>();
            }
    
            template <unsigned int I>
            const T_& get() const {
                return (*reinterpret_cast<Vec<N_, T_>*>(this)).get<IndexAt<I>::Result>();
            }
    
            template <typename T1>
            T_& operator[](T1 i) {
                assert(i >= 0 && i < NIndicies);
                return (*reinterpret_cast<Vec<N_, T_>*>(this))[IndexAtI(i)];
            }
        };
    


  • Hm... hab mir das Problem nicht so genau angeschaut, aber wie wäre es mit so etwas?

    template<int... List> struct indices{}; 
    
    template<int N, int... Tail>
    struct make_indices : make_indices<N-1,N-1,Tail...> {};
    
    template<int... Tail>
    struct make_indices<0,Tail...> {
      typedef indices<Tail...> type;
    }; 
    
    template<int... List> std::vector<unsigned int> make_index_vector(indices<List...>)
    {
        return std::vector<unsigned int>({IndexAt<List>...::Result});  //Auweia, keine Ahnung, ob das ... hinter Result muss und ob das mit der initializer_list so funktioniert
    }
    
    template<typename T1> unsigned int IndexAtI(T1 index)
    {
        static std::vector<unsigned int> vec = make_index_vector(make_indices<std::tuple_size<ParamTuple>::value>::type());
        return vec[index];
    }
    

    Die Ideen stammen weitgehend aus diesem Thread: http://www.c-plusplus.net/forum/p2023356#2023356



  • template <typename T1> 
             unsigned int IndexAtI(T1 i) { 
                     // ??? 
             }
    

    Ich verstehe nicht was T1 hier sein soll. Soll i ein (numerischer) Index sein? Dann verstehe ich nicht wieso du nicht gleich size_t nimmst.

    Falls i also ein Index von 0 ... (NIndicies-1) ist, dann müsste doch sowas gehen:

    template <typename T1> 
             unsigned int IndexAtI(T1 i) { 
                 static unsigned int const foo[] = { Indicies_... }; // k.a. ob die Schreibweise passt, ich kenne variadic templates bisher nur vom Lesen
                 assert(i >= 0 && i < NIndicies);
                 return foo[i];
             }
    


  • Du glaubst es nicht, aber ich bin eben in etwa auf die selbe Lösung gekommen die einfach simpel ist. Die Lösung von wxSkip ist an manchen Stellen sinnfrei, hat mich aber auf die Idee mit den Initializer-Lists gebracht, danke dafür 🙂

    Hier wäre meine Lösung, die der von hustbaer entspricht.

    template <typename T1>
            unsigned int IndexAtI(T1 i) {
                static std::vector<unsigned int> indicies = { Indicies... };
                return indicies[i];
            }
    

    Gut, das assert werd ich noch einbaun. Dann hat sich das ja erledigt, danke an alle Beteiligten 🙂



  • Ich würd's mit einem Array machen, so wie es hustbaer gezeigt hat. Das erspart Dir die dynamische Initialisierung des vectors.

    Ich finde den Ansatz recht interessant. Es scheint, als wäre das eine neue Möglichkeit, Lookup-Tabellen zur Compile-Zeit anzulegen. Bisher habe ich so etwas nur mal über ein entsprechendes Makefile gemacht...

    tabelle.o: tabelle.cpp table.inc
    
    table.inc: $(MAKE_TABLE_BINARY)
        $(MAKE_TABLE_BINARY) table.inc
    
    $(MAKE_TABLE_BINARY): make_table.o
    
    // tabelle.cpp
    
    extern const int dings[] = {
    #include "table.inc"
    };
    

    (so in etwa)



  • krümelkacker schrieb:

    Ich würd's mit einem Array machen, so wie es hustbaer gezeigt hat. Das erspart Dir die dynamische Initialisierung des vectors.

    Plus eine Indirektion zur Laufzeit.
    Und der Compiler sollte dadurch den ganzen Aufruf wegoptimieren können, wenn man als Parameter eine Konstante übergibt.

    Für mich also ein Fall, wo ganz eindeutig ein rohes Array statt std::vector angesagt ist.


  • Mod

    Nur der Vollständigkeit halber:

    template <typename T, unsigned... Indizes>
    struct Foo;
    
    template <typename T, unsigned I0, unsigned... Indizes>
    struct Foo<T, I0, Indizes...>
    {
        static constexpr unsigned IndexAtI(int i)
        {
            return i == 0 ? I0 : Foo<T, Indizes...>::IndexAtI( i - 1 );
        }
    };
    
    template <typename T>
    struct Foo<T>
    {
        static constexpr unsigned IndexAtI(int i)
        {
            throw std::out_of_range("foo");
        }
    };
    

    Besser als die Array Variante dürfte das nicht sein.
    Ganz nebenbei gesagt halte ich die Verwendung von enum hier für verfehlt, in Zeiten von C++0x gibt es dafür einfach keine Rechtfertigung mehr.



  • @camper:
    Sagmal, kann man in C++0x eigentlich auch nach constexpr überladen?

    Ich befürchte fast nicht... aber oft ist es ja (wie hier) so, dass Funktionen mit constexpr nur umständlich implementiert werden können. Ohne constexpr dagegen hübsch einfach.

    Da wäre es ja wünschenswert, wenn der Compiler nur bei Compiletime-Auswertung die constexpr Variante nimmt, bei Runtime-Auswertung dagegen die nicht- constexpr Variante.


  • Mod

    hustbaer schrieb:

    @camper:
    Sagmal, kann man in C++0x eigentlich auch nach constexpr überladen?

    Ich befürchte fast nicht... aber oft ist es ja (wie hier) so, dass Funktionen mit constexpr nur umständlich implementiert werden können. Ohne constexpr dagegen hübsch einfach.

    Da wäre es ja wünschenswert, wenn der Compiler nur bei Compiletime-Auswertung die constexpr Variante nimmt, bei Runtime-Auswertung dagegen die nicht- constexpr Variante.

    Ich glaube nicht, dazu müsste man ja constepxr an die entsprechenden Parameter schreiben können:

    void foo(constexpr int i);
    

    oder so. Das ist nicht zulässig, ich könnte mir aber gut vorstellen, dass man die Sprache in 15 Jahren dahin eweitert wird 🙂

    Nebenbei lässt sich auch die Arrayvariante als constexpr schreiben:

    template <unsigned... Indices>
    struct Foo
    {
        static constexpr unsigned indices[] = { Indices... };
        static constexpr unsigned IndexAtI(std::size_t i) { return indices[i]; }
    };
    

    bzw.

    template <unsigned... Indices>
    struct Foo
    {
        static const unsigned indices[sizeof... Indices];
        static constexpr unsigned IndexAtI(std::size_t i) { return indices[i]; }
    };
    
    template <unsigned... Indices>
    const unsigned Foo<Indices...>::indices[] = { Indices... };
    

    solange g++ constexpr noch nicht vollständig umsetzt.



  • camper schrieb:

    template <unsigned... Indices>
    struct Foo
    {
        static const unsigned indices[sizeof... Indices];
        static constexpr unsigned IndexAtI(std::size_t i) { return indices[i]; }
    };
    
    template <unsigned... Indices>
    const unsigned Foo<Indices...>::indices[] = { Indices... };
    

    solange g++ constexpr noch nicht vollständig umsetzt.

    Müsste das nicht static const unsigned indices[sizeof...(Indices) / sizeof(unsigned)]; heißen?



  • @camper:
    Joha, die constexpr Version mit Array ist gut. Ich wusste nicht dass das geht.

    Ich glaube nicht, dazu müsste man ja constepxr an die entsprechenden Parameter schreiben können

    Nö, wieso? Das wäre doch bloss Definitionssache.
    Man könnte es so schreiben:

    constexpr int foo (int x) { ... }
    int foo(int x) { ... }
    

    Und beim Aufruf guckt der Compiler ob er den Aufruf compiletime erledigen kann. Wenn ja nimmt er die 1. Variante, wenn nein die 2.

    static const i = foo(123); // ruft [constexpr foo(int)] auf
    int main()
    {
        std::cout << foo(time(0)) << "\n"; // ruft [foo(int)] auf
        std::cout << foo(sizeof(int)) << "\n"; // ruft [constexpr foo(int)] auf
    }
    

    Dazu wäre natürlich nötig, dass constexpr zum Teil der Signatur wird (wie das auf this bezogenen const bei Memberfunktionen).



  • hustbaer schrieb:

    @camper:
    Sagmal, kann man in C++0x eigentlich auch nach constexpr überladen?

    Ich versteh die Motivation noch nicht ganz. Du würdest also eine Funktion gern 2mal implementieren (statt 1mal) weil die zweite, überflüssige Implementierung hübscher aussieht?

    wxSkip schrieb:

    Müsste das nicht static const unsigned indices[sizeof...(Indices) / sizeof(unsigned)]; heißen?

    Nein. sizeof... liefert die Größe des "Packs" nicht in Bytes sondern einfach in der Anzahl der Elemente im "Pack".



  • krümelkacker schrieb:

    wxSkip schrieb:

    Müsste das nicht static const unsigned indices[sizeof...(Indices) / sizeof(unsigned)]; heißen?

    Nein. sizeof... liefert die Größe des "Packs" nicht in Bytes sondern einfach in der Anzahl der Elemente im "Pack".

    Das ist mir neu. Kann man die Größe des Packs in Bytes dann durch sizeof(Indices...) bekommen?



  • krümelkacker schrieb:

    hustbaer schrieb:

    @camper:
    Sagmal, kann man in C++0x eigentlich auch nach constexpr überladen?

    Ich versteh die Motivation noch nicht ganz. Du würdest also eine Funktion gern 2mal implementieren (statt 1mal) weil die zweite, überflüssige Implementierung hübscher aussieht?

    Nein. Sondern weil die zweite, überflüssige Implementierung schneller läuft. Was mir compiletime (relativ) wurscht ist, runtime aber nicht unbedingt.

    Und warum soll der User sich drum kümmern (indem er Funktionen mit verschiedenen Namen macht), wenn der Compiler die nötigen Infos eh schon hat.



  • wxSkip schrieb:

    krümelkacker schrieb:

    wxSkip schrieb:

    Müsste das nicht static const unsigned indices[sizeof...(Indices) / sizeof(unsigned)]; heißen?

    Nein. sizeof... liefert die Größe des "Packs" nicht in Bytes sondern einfach in der Anzahl der Elemente im "Pack".

    Das ist mir neu. Kann man die Größe des Packs in Bytes dann durch sizeof(Indices...) bekommen?

    Ich schätze um die Grösse des Packs in Bytes zu bekommen, wird man ein reukrsives Klassentemplate brauchen.



  • template <typename...>
    struct variadic_sizeof;
    
    template <>
    struct variadic_sizeof<>
    {
            static const int value = 0;
    };
    
    template <typename Head, typename... Tail>
    struct variadic_sizeof<Head, Tail...>
    {
            static const int value = sizeof(Head) + variadic_sizeof<Tail...>::value;
    };
    

    🤡



  • hustbaer schrieb:

    krümelkacker schrieb:

    hustbaer schrieb:

    @camper:
    Sagmal, kann man in C++0x eigentlich auch nach constexpr überladen?

    Ich versteh die Motivation noch nicht ganz. Du würdest also eine Funktion gern 2mal implementieren (statt 1mal) weil die zweite, überflüssige Implementierung hübscher aussieht?

    Nein. Sondern weil die zweite, überflüssige Implementierung schneller läuft. Was mir compiletime (relativ) wurscht ist, runtime aber nicht unbedingt.

    Dann verzichtet man aber auf die Garantie, dass die Funktion, egal ob zur Compiletime oder Runtime berechnet, immer das selbe Ergebnis hat. Das verspricht schöne Debugsitzungen, wenn man in einer der beiden Implementeirungen einen Bug hat und foo(i) sich plötzlich unterschiedlich verhält, sobald der Compiler den Wert für i vorher weiß. Verglichen mit dem relativ geringem Nutzen (wenn man einen bestimmten Wert unbedingt zur Compilezeit braucht, tut ja eine andere Funktion wirklich nicht weh) finde ich die jetzige Lösung besser.


Anmelden zum Antworten