C++0x variadic templates Elemente zur Runtime



  • 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.



  • ipsec schrieb:

    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

    Davor möchte ich aber nicht beschützt werden, wenn ich dafür Laufzeitkosten zahlen muß.


  • Mod

    Von einer derartigen transparenten Überladung der gleichen Funktion halte wenig, eben genau deshalb weil es prinzipiell nicht feststellbar ist, welche Funktion aufgerufen wird, zudem kriegt man dafür auch kaum eine entsprechend brauchbare ODR-Regel formuliert.
    Mein Ansatz war anders gedacht: ein constexpr als Teil eines Parameters wirkt einschränkend, so dass das Argument für den jeweiligen Parameter ein konstanter Ausdruck sein muss. Die Überladungsauflösung könnte analog zu den üblichen const/non-const Überladungen funktionieren.
    Dann könnte man z.B. haben

    void foo(constexpr int, constexpr int); // (1)
    void foo(int, constexpr int);  // (2)
    
    int a = 1;
    foo(1, 1); // ruft (1) auf
    foo(a, 1); // ruft (2) auf
    foo(a, a); // ill-formed
    


  • volkard schrieb:

    ipsec schrieb:

    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

    Davor möchte ich aber nicht beschützt werden, wenn ich dafür Laufzeitkosten zahlen muß.

    Musst du ja nicht. Nichts hindert dich, die Funktion zu optimieren. Dann geht eben nur constexpr nicht mehr und du must dafür eine neue Funktion nehmen (oder gleich eine Metafunktion) - wo ist das Problem? Relevant wird das ganze ja eigentlich nur, wenn sich eine Funktion als Performancefresser herausstellt und unbedingt zur Compilezeit berechnet werden muss. Dann hilft dir constexpr auch nicht weiter, das ist zwar ein Hinweis an den Compiler, dass die Funktion zur Compilezeit berechnet werden kann, aber kein Zwang, das wenn möglich tatsächlich zu tun.
    Dann also doch gleich eine Metafunktion (oder eben eine zusätzliche constexpr -Variante und auf den Compiler hoffen) und man hat zusätzlich noch eine Dokumentation, dass im konkreten Fall eine Berechnung zur Compiletime erwünscht ist.

    Ich sehe auch durchaus Vorteile, würde der Compiler das automatisch machen. Aber dann sehe ich eben schwer zu findende Bugs bei unerheblich weniger Aufwand.



  • camper schrieb:

    foo(a, 1); // ruft (2) auf
    foo(a, a); // ill-formed
    

    Prinzipiell könnte in diesem Fall der Compiler beide Male (1) aufrufen, da a sich ja nicht ändert. Mit solchen Regeln würde man den Compiler also nur in seiner Optimierungsfreiheit beschränken. Und davon abgesehen: welchen genauen Vorteil hast du in foo , wenn du weißt, dass bestimmte Parameter compiletime-konstante Werte sind?


  • Mod

    ipsec schrieb:

    camper schrieb:

    foo(a, 1); // ruft (2) auf
    foo(a, a); // ill-formed
    

    Prinzipiell könnte in diesem Fall der Compiler beide Male (1) aufrufen, da a sich ja nicht ändert. Mit solchen Regeln würde man den Compiler also nur in seiner Optimierungsfreiheit beschränken.

    Wieso? Er kann ja immer noch versuchen, foo-2 inline zur COmpilezeit auszuwerten, wenn dessen Code das hergibt.

    ipsec schrieb:

    Und davon abgesehen: welchen genauen Vorteil hast du in foo , wenn du weißt, dass bestimmte Parameter compiletime-konstante Werte sind?

    Nun, einmal kann man eine Überladung so wie von hustbaer gewünscht erreichen, dann macht man die Funktion selbst noch constexpr.
    Eine andere Optimierungsmöglichkeit wäre z.B. gegeben, wenn assembler im Spiel ist, ist ein bestimmter Parameter ein konstanter Ausdruck, kann man ggf. Register einsparen und mit immediates arbeiten (wobei mir durchaus bewusst ist, dass es dafür bereits Möglichkeiten gibt, je nach Eigenschaften der Parameter alternativen Code anzubieten).
    Abgeshen davon kommt mir das Ganze so etwas sauberer vor; ist constexpr Teil des Typ muss z.B. nicht groß überlegen, wass passiert wenn man die Funktion über einen Zeiger oder z.B. ein std::function-Objekt benutzt.



  • camper schrieb:

    Von einer derartigen transparenten Überladung der gleichen Funktion halte wenig, eben genau deshalb weil es prinzipiell nicht feststellbar ist, welche Funktion aufgerufen wird, zudem kriegt man dafür auch kaum eine entsprechend brauchbare ODR-Regel formuliert.

    Hm.
    Hmmmmmm.
    Also ich glaub' dir das jetzt einfach mal, auch wenn ich mir nicht ganz sicher bin was genau das Problem wäre.
    Alles unter einen Hut zu bringen (ODR, Funktionszeiger, Overload-Resolution etc.) könnte auf jeden Fall durchaus schwierig werden.

    Mein Ansatz war anders gedacht: ein constexpr als Teil eines Parameters wirkt einschränkend, so dass das Argument für den jeweiligen Parameter ein konstanter Ausdruck sein muss. Die Überladungsauflösung könnte analog zu den üblichen const/non-const Überladungen funktionieren.

    Hm. Das heisst, das ginge dann auch nicht:

    constexpr int foo(constexpr int);
    
    int (*fp)(int) = foo; // error: no conversion from [constexpr int (*)(constexpr int)] to [int (*)(int)]
    


  • camper schrieb:

    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.

    Du hast recht, ich kann das obere noch nicht übersetzten. Aber laut GCC Seite ist es implementiert: http://gcc.gnu.org/projects/cxx0x.html. Ich arbeite mit GCC 4.6, was haben die denn verbockt 😞


Anmelden zum Antworten