campers TMP-Aufgabe(n): 7


  • Mod

    Sone schrieb:

    Überhaupt, irgendwo muss es ja die Rekursion geben - das meintest du selbst. Und da ist die Rekursion.

    Um die Indexliste zu erzeugen.

    template<int ... args>
    struct index_list
    {
        static constexpr int arr[]{args...};
        static constexpr int size = sizeof...(args);
        using type = index_list;
    };
    
    template <typename T, typename U> struct concat_list;
    template <int... i, int... j>
    struct concat_list<index_list<i...>,index_list<j...>>
    : index_list<i..., (sizeof...(i)+i)..., (2*sizeof...(i)+j)...> {};
    
    template <int N> struct make_index_list
    : concat_list<typename make_index_list<N/2>::type, typename make_index_list<N%2>::type> {};
    template <> struct make_index_list<1> : index_list<0> {};
    template <> struct make_index_list<0> : index_list<> {};
    
    template <int i> struct rack { int v; };
    
    template <typename T, typename = typename make_index_list<T::size>::type> struct acc;
    
    template <int... i, int... j> struct acc<index_list<i...>, index_list<j...>>
    : rack<-1>, rack<j>...
    {
        constexpr acc() : rack<-1>{0}, rack<j>{rack<j-1>::v+i}... {}
        static const int value;
    };
    
    template <int... i, int... j>
    const int acc<index_list<i...>, index_list<j...>>::value = acc().rack<sizeof...(j)-1>::v;
    


  • rack<-1>{0}, rack<j>{rack<j-1>::v+i}...
    

    Natürlich...
    Ja, das ist schön. Damit kann man alles schöner schreiben, auch partial_sum und wahrscheinlich auch multiply...

    Und welche ist jetzt schneller?

    Edit: Das hätte ich doch sehen müssen...:

    template <typename V> struct fib; 
    template <typename T, T... i> struct fib<value_list<T, i...>> 
        : tagged_value<0,T>, tagged_value<1,T>, tagged_value<j+2, T>... 
    { 
        constexpr partial_sum() : tagged_value<0,T>(0), tagged_value<1,T>(1), tagged_value<j+2, T>( tagged_value<j,T>::v + tagged_value<j+1,T>::v )... {} 
    };
    

    Mit dem Trick lässt sich alles schöner schreiben.


  • Mod

    Sone schrieb:

    Und welche ist jetzt schneller?

    clang steigt bei großer constexpr-Rekursionstiefe aus
    Mit g++ 4.8.1 erhalte cih für die constexpr-Variante (jweils wieder mit und ohne Instantiierung von accumulate, die Differenz der Werte ist von Interesse).

    X=100 utime=0.19 res=137856 utime=0.18 res=136272
    X=150 utime=0.18 res=140576 utime=0.18 res=136800
    X=225 utime=0.19 res=142240 utime=0.18 res=137136
    X=337 utime=0.19 res=152496 utime=0.17 res=139168
    X=505 utime=0.20 res=160896 utime=0.18 res=141216
    X=757 utime=0.21 res=188928 utime=0.19 res=147392
    X=1135 utime=0.25 res=265072 utime=0.21 res=166000
    X=1702 utime=0.31 res=381856 utime=0.25 res=179472
    X=2553 utime=0.42 res=661360 utime=0.33 res=235328
    X=3829 utime=0.71 res=1253536 utime=0.49 res=314448
    X=5743 utime=1.28 res=2683328 utime=0.87 res=534880
    X=8614 utime=2.57 res=5789856 utime=1.67 res=1034896
    X=12921 utime=5.42 res=12672320 utime=3.46 res=2026080
    

    Mein Code:

    X=100 utime=0.19 res=146224 utime=0.19 res=136416
    X=150 utime=0.21 res=151648 utime=0.18 res=137024
    X=225 utime=0.23 res=158432 utime=0.19 res=137440
    X=337 utime=0.26 res=170960 utime=0.19 res=139360
    X=505 utime=0.30 res=186384 utime=0.19 res=141632
    X=757 utime=0.39 res=217072 utime=0.20 res=147888
    X=1135 utime=0.59 res=269824 utime=0.22 res=166928
    X=1702 utime=1.02 res=331872 utime=0.25 res=180640
    X=2553 utime=1.85 res=464032 utime=0.33 res=236896
    X=3829 utime=3.73 res=654192 utime=0.49 res=316448
    X=5743 utime=7.71 res=1068160 utime=0.86 res=538016
    X=8614 utime=16.63 res=1723568 utime=1.67 res=1038784
    X=12921 utime=36.29 res=3103984 utime=3.45 res=2031216
    

    Schön linearer Speicherbedarf, dafür unterirdische Geschwindgikeit.

    Mal noch zum Vergleich

    template <typename T,
              typename = typename make_index_list<T::size/2>::type,
              typename = typename make_index_list<T::size%2>::type> struct acc;
    
    template <typename T, int... j, int... k>
    struct acc<T, index_list<j...>,index_list<k...>>
    : std::integral_constant<int, acc<index_list<T::arr[j]...>>::value+
                                  acc<index_list<T::arr[sizeof...(j)+j]...>>::value+
                                  acc<index_list<T::arr[2*sizeof...(j)+k]...>>::value>
    {};
    
    template <>
    struct acc<index_list<>,index_list<>,index_list<>> : std::integral_constant<int,0> {};
    template <int i>
    struct acc<index_list<i>,index_list<>,index_list<0>> : std::integral_constant<int,i> {};
    

    mit gcc 4.8.1

    X=100 utime=0.22 res=158560 utime=0.19 res=136432
    X=150 utime=0.24 res=181152 utime=0.18 res=137120
    X=225 utime=0.27 res=199040 utime=0.18 res=137584
    X=337 utime=0.33 res=275280 utime=0.19 res=139648
    X=505 utime=0.40 res=340256 utime=0.19 res=141584
    X=757 utime=0.60 res=577792 utime=0.21 res=147968
    X=1135 utime=1.01 res=1143088 utime=0.22 res=166976
    X=1702 utime=1.49 res=1855312 utime=0.26 res=180672
    X=2553 utime=2.76 res=3846768 utime=0.33 res=236960
    X=3829 utime=4.84 res=7397792 utime=0.56 res=316496
    X=5743 utime=9.71 res=16665488 utime=0.88 res=538240
    

    also schlechter als die anderen Varianten, wobei sich das sicher etwas verbessern ließe, wenn ein größerer Faktor als 2 verwendet wird.

    Mein Code+clang

    X=100 utime=0.21 res=102400 utime=0.21 res=99744
    X=150 utime=0.22 res=103904 utime=0.21 res=100000
    X=225 utime=0.23 res=106128 utime=0.21 res=100256
    X=337 utime=0.25 res=109456 utime=0.20 res=100800
    X=505 utime=0.30 res=114416 utime=0.20 res=101488
    X=757 utime=0.37 res=122064 utime=0.21 res=102560
    X=1135 utime=0.60 res=133344 utime=0.22 res=104256
    X=1702 utime=1.11 res=150368 utime=0.22 res=106480
    X=2553 utime=2.12 res=175776 utime=0.22 res=110112
    X=3829 utime=5.27 res=214336 utime=0.23 res=115200
    X=5743 utime=13.16 res=272384 utime=0.25 res=123888
    X=8614 utime=35.95 res=360032 utime=0.27 res=135584
    X=12921 utime=87.30 res=491056 utime=0.29 res=155376
    X=19381 utime=193.86 res=685744 utime=0.33 res=184624
    X=29071 utime=456.63 res=982000 utime=0.40 res=220384
    X=43606 utime=1012.73 res=1421840 utime=0.47 res=290624
    X=65409 utime=2432.28 res=2085632 utime=0.64 res=384944
    X=98113 utime=5285.76 res=3058112 utime=0.82 res=520656
    

    Die letzte Variante mit clang

    X=100 utime=0.24 res=107776 utime=0.21 res=99792
    X=150 utime=0.26 res=113488 utime=0.20 res=100032
    X=225 utime=0.28 res=118176 utime=0.20 res=100336
    X=337 utime=0.34 res=130624 utime=0.20 res=100816
    X=505 utime=0.37 res=141056 utime=0.21 res=101488
    X=757 utime=0.50 res=168128 utime=0.21 res=102592
    X=1135 utime=0.71 res=216352 utime=0.21 res=104272
    X=1702 utime=0.91 res=255440 utime=0.22 res=106528
    X=2553 utime=1.45 res=357184 utime=0.22 res=110176
    X=3829 utime=2.10 res=448848 utime=0.22 res=115200
    X=5743 utime=3.75 res=675040 utime=0.24 res=124176
    X=8614 utime=7.12 res=1074704 utime=0.26 res=136624
    X=12921 utime=12.10 res=1403328 utime=0.29 res=155696
    X=19381 utime=24.27 res=2250448 utime=0.32 res=184448
    X=29071 utime=47.27 res=3022464 utime=0.39 res=221776
    X=43606 utime=103.71 res=4885888 utime=0.48 res=291088
    X=65409 utime=225.23 res=6634432 utime=0.65 res=386064
    X=98113 segfault
    


  • Ja, diese Aufteil-Variante hätte ich auch noch vorgeschlagen.

    Schön linearer Speicherbedarf, dafür unterirdische Geschwindgikeit.

    Ja, aber wo denkst du überschneiden sich die Geschwindigkeiten?

    Im Übrigen stelle ich in Frage, ob diese - obgleich schöne Variante - überhaupt richtig ist. value ist nämlich nicht constexpr .
    Ich habe dieselbe Form von Lösung schon gefunden, sie aber abgetan, weil sie ja nicht constexpr ist...

    template<int ... args>
    struct index_list
    {
        static constexpr int size = sizeof...(args);
        using type = index_list;
    };
    
    template <typename T, typename U> struct concat_list;
    template <int... i, int... j>
    struct concat_list<index_list<i...>,index_list<j...>>
    : index_list<i..., (sizeof...(i)+i)..., (2*sizeof...(i)+j)...> {};
    
    template <int N> struct make_index_list
    : concat_list<typename make_index_list<N/2>::type, typename make_index_list<N%2>::type> {};
    template <> struct make_index_list<1> : index_list<0> {};
    template <> struct make_index_list<0> : index_list<> {};
    
    template<typename T, typename =  typename make_index_list<T::size>::type> // Der Trick mit dem default-Argument ist auch gut.
    struct accumulate;
    template<int ... args, int ...ind>
    struct accumulate<index_list<args...>, index_list<ind...>>
    {
        static const int arr[];
        static const int value;
    };
    
    template<int ... args, int ... ind>
    const int accumulate<index_list<args...>, index_list<ind...>>::arr[]{ 0, (arr[ind] + args)... }; // Bei initializer-lists ist die Auswertungsreihenfolge definiert.
    template<int ... args, int ... ind>
    const int accumulate<index_list<args...>, index_list<ind...>>::value = arr[sizeof...(args)]; // ... und value ist nicht constexpr.
    

  • Mod

    Ein constexpr kann problemlos hinzugefügt werden

    template <int... i, int... j>
    constexpr const int acc<index_list<i...>, index_list<j...>>::value = acc().rack<sizeof...(j)-1>::v;
    

    So oder so kann v dann in konstanten Ausdrücken verwendet werden.
    Mit dem Array funktioniert das nicht (wobei ich den Standard an dieser Stelle für unklar halte).



  • template <int... i, int... j> 
    constexpr const int acc<index_list<i...>, index_list<j...>>::value
    

    Was ist das? oO Wie soll das gehen?

    Edit: Das geht doch überhaupt nicht!
    Aber du hast Recht, v ist constexpr.



  • Sone schrieb:

    Aber du hast Recht, v ist constexpr .

    Nö, auch das nicht!

    note: 'constexpr acc<index_list<i ...>, index_list<j ...> >::acc() [with int ...i = {1, 2, 3}; int ...j = {0, 1, 2}]' is not usable as a constexpr function because:|
    

    (Es folgt nichts)
    Kann auch ein Bug sein. Ist der GCC 4.8.1...

    Nach GCC 4.8.1 jedenfalls ist deine Lösung nicht statischer als meine. Auch meine lässt sich als Array-Bound verwenden, nicht aber als initializer für eine constexpr -Variable.


  • Mod

    Ich betrachte das mal als Bug (von gcc).
    Der entscheidene Test sollte sein, ob nach der Definition value als konstanter Ausdruck verwendet werden kann.

    constexpr int foo = accumulate<list>::value;
    
    extern const int foo;
    constexpr int foo = 42;
    

    ist legal (und sowieso die einzige Möglichkeit, da constexpr immer einen Initialisierer benötigt, liese sich eine solche Variable sonst gar nicht nur deklarieren).



  • Die Map:

    enum class lookup_mode { FirstSec, SecFirst };
    
    template <lookup_mode lk, int, typename T, typename, typename = typename make_index_list<T::size>::type>
    struct lookup;
    
    template <lookup_mode lk, int key, int... first, int ... second,  int... indices>
    struct lookup<lk, key, index_list<first...>, index_list<second...>, index_list<indices...>>:
        rack<-1>, rack<indices>...
    {
        constexpr lookup() :
            rack<-1>{0}, rack<indices>{ ( rack<indices-1>::v ?
                                    0
                                  : ( lk == lookup_mode::FirstSec ?
                                      (first == key) * second
                                    : (second == key) * first ) ) + rack<indices-1>::v } ... {}
    
        static const int value;
    };
    
    template <lookup_mode lk, int val, int... i, int ... k, int... indices>
    const int lookup<lk, val, index_list<i...>, index_list<k...>, index_list<indices...>>::value = lookup{}.rack<sizeof...(indices)-1>::v;
    
    int main()
    {
        std::cout << lookup<lookup_mode::FirstSec, 8, index_list<4, 8, 6>, index_list<0, 47, 95>>::value << '\n';
        std::cout << lookup<lookup_mode::SecFirst, 6, index_list<4, 74, 68, 6>, index_list<0, 6, 6, 95>>::value << '\n';
    }
    

    Auch hier wäre deine Idee von typedefs mit template-parameter packs schön, das würde besser passen.

    using pack_typedef_name = ... std::conditional< lk == lookup_mode::FirstSec, tpp_wrapper<first...>, tpp_wrapper<second...> >::type::pack;
    

    (Die Ellipse gibt an, dass pack ein parameter-pack ist)

    camper schrieb:

    (und sowieso die einzige Möglichkeit, da constexpr immer einen Initialisierer benötigt, liese sich eine solche Variable sonst gar nicht nur deklarieren).

    Aber schon, wenn sie eine statische Membervariable ist (wie ja auch in [dcl.constexpr]/1 erklärt). Das implementiert aber GCC 4.8.1 nicht korrekt...



  • template <int Key, bool Flipped=false> struct hook { int value; };
    
    template <typename, typename> struct static_map;
    
    template <int... first, int ... second>
    struct static_map<index_list<first...>, index_list<second...> >
      : hook<first>..., hook<second, true>... {
      static_map() : hook<first>{second}..., hook<second, true>{first}... {}
    
      template <int V> static constexpr int lookup_key() {
        return static_cast<hook<V> >(static_map{}).value;
      }
      template <int V> static constexpr int lookup_value() {
        return static_cast<hook<V, true> >(static_map{}).value;
      }
    };
    

  • Mod

    Sone schrieb:

    camper schrieb:

    (und sowieso die einzige Möglichkeit, da constexpr immer einen Initialisierer benötigt, liese sich eine solche Variable sonst gar nicht nur deklarieren).

    Aber schon, wenn sie eine statische Membervariable ist (wie ja auch in [dcl.constexpr]/1 erklärt).

    Was wird da erklärt?

    9.4.2/3
    ...
    A static data member of literal type can be declared in the class definition with the constexpr specifier; if so, its declaration shall specify a brace-or-equal-initializer in which every initializer-clause that is an assignment-expression is a constant expression.


  • Mod

    Das Problem mit dem Array lässt sich auf diesen Fall reduzieren

    struct A
    {
         int x,y;
         constexpr A() : x{y}, y{x} {}
    };
    
    constexpr A a{}; // ok, da zuerst Zero-Initialisierung
    
    constexpr int foo[] = { foo[0] };
    constexpr int bar[] = { 0, bar[0] };
    

    Die Definition von z wird akzeptiert, die von foo und bar von clang nicht (und g++ streikt sowieso).
    Meiner Ansicht nach haben beide Compiler unrecht:

    n3337 schrieb:

    A conditional-expression is a core constant expression unless it involves one of the following as a potentially evaluated subexpression (3.2), but subexpressions of logical AND (5.14), logical OR (5.15), and conditional (5.16) operations that are not evaluated are not considered [ Note: An overloaded operator invokes a function.—end note ]:
    ...
    — an lvalue-to-rvalue conversion (4.1) unless it is applied to
    ____— a glvalue of integral or enumeration type that refers to a non-volatile const object with a preceding initialization, initialized with a constant expression, or
    ____— a glvalue of literal type that refers to a non-volatile object defined with constexpr, or that refers to a sub-object of such an object, or
    ____— a glvalue of literal type that refers to a non-volatile temporary object whose lifetime has not ended, initialized with a constant expression;
    ...

    Der markierte Teil trifft hier auf beide Fälle zu. Insbesondere wird nicht verlangt, dass die Initialisierung bereits abgeschlossen ist.

    struct foo
    {
        static constexpr int x[] = { x[0] };
    };
    const int foo::x[];
    

    kann hingegen nicht funktionieren, denn die Deklaration in der Klasse ist keine Definition.



  • denn die Deklaration in der Klasse ist keine Definition.

    Jetzt bin ich völlig verwirrt.

    Damit ist das in der Klasse aber keine Initialisierung...

    P.S.: Arch-Linux ist drauf. Ist voll toll. 👍
    (Außer das irgendwie GNOME nicht richtig die Programme startet, muss immer in der Konsole thunderbird schreiben... aber dafür gibt es ja Skripte)


  • Mod

    Sone schrieb:

    denn die Deklaration in der Klasse ist keine Definition.

    Jetzt bin ich völlig verwirrt.

    static data member werden in der Klassendefinition bloß deklariert, nicht definiert; das ist nichts Neues (gerade deshalb muss ja oft (=odr-use) zusätzlich eine Definition ausserhalb der Klassendefinition erfolgen).

    Die zitierte Regel stellt aber ausdrücklich auf die Definition ab

    ____— a glvalue of literal type that refers to a non-volatile object defined with constexpr, or that refers to a sub-object of such an object, or



  • Ok, ich habe wieder einen klaren Kopf:

    A declaration is a definition unless [...] it declares a static data member in a class definition

    Somit kann das keine Definition sein.
    Edit: Das steht auch nochmal oben von dir, verzeih'.

    constexpr const int* xp = addr(x);// OK:(const int*)&(const int&)x is an address constant expression
    

    MOMENT MAL! Davon wusste ich nichts...
    Bzw. das müsste ich eigentlich - ich hab bei camper schon Memberfunktions-Zeiger als Template-Parameter gesehen...
    Das ist interessant. Sehr interessant.

    camper schrieb:

    Meiner Ansicht nach haben beide Compiler unrecht:

    n3337 schrieb:

    A conditional-expression is a core constant expression unless it involves one of the following as a potentially evaluated subexpression (3.2), but subexpressions of logical AND (5.14), logical OR (5.15), and conditional (5.16) operations that are not evaluated are not considered [ Note: An overloaded operator invokes a function.—end note ]:
    ...
    — an lvalue-to-rvalue conversion (4.1) unless it is applied to
    ____— a glvalue of integral or enumeration type that refers to a non-volatile const object with a preceding initialization, initialized with a constant expression, or
    ____— a glvalue of literal type that refers to a non-volatile object defined with constexpr, or that refers to a sub-object of such an object, or
    ____— a glvalue of literal type that refers to a non-volatile temporary object whose lifetime has not ended, initialized with a constant expression;
    ...

    Der markierte Teil trifft hier auf beide Fälle zu. Insbesondere wird nicht verlangt, dass die Initialisierung bereits abgeschlossen ist.

    Natürlich nicht. Denn hier ist die Deklaration eine Definition ([basic.def]/2) und der Punkt der Deklaration (point of declaration) ist ja vor dem Initializer, hier die initializer-list. Damit sollte das doch gehen, nicht wahr?
    Edit: Jaja, das war gerade falsch - Standard falsch gelesen.

    Und foo[0] ist dann was? Oder hat der Standard dafür wieder ein kleines Hintertürchen?


  • Mod

    Sone schrieb:

    Und foo[0] ist dann was?

    Ein lvalue, dass auf das erste Element des Arrays verweist (und ein konstanter Ausdruck: foo[0] ist *(foo+0) - die array-to-pointer-Konvertierung ist konstant, weil sie auf ein Objekt mit statischer Speicherdauer verweist, 0 ist ein Literal und + und * sind nicht in der Liste der Ausnahmen enthalten, also unproblematisch; Problematisch könnte also nur die finale Umwandlung des lvalues foo[0] in ein rvalue sein).


Anmelden zum Antworten