std::array mit constexpr initialisieren



  • Hallo Forum!

    Ich würde gerne eine std::array<> mit einer constexpr Funktion initialisieren. Dazu habe ich mir folgendes ausgedacht:

    #include <array>
    
    template <int size> constexpr
    std::array<int, size> foo(std::array<int, size> buf = {}, int m = 0)
    {
      return m == size ? buf : (buf[m] = m, foo<size>(buf, m+1));
    }
    
    auto const bar = foo<42>();
    
    int main()
    {
      return 0;
    }
    

    Das scheint auch zu kompilieren, aber wenn ich dann einen Wert aus dem konstanten array für z.B. eine weitere array Deklaration nutzen möchte, scheint das doch nicht konstant zu sein:

    #include <array>
    
    template <int size> constexpr
    std::array<int, size> foo(std::array<int, size> buf = {}, int m = 0)
    {
      return m == size ? buf : (buf[m] = m, foo<size>(buf, m+1));
    }
    
    auto const bar = foo<42>();
    
    int buf[bar[5]];
    
    int main()
    {
      return 0;
    }
    
    $ clang++ -std=c++11 -stdlib=libc++ constexprtest.cpp
    constexprtest.cpp:11:5: error: variable length array declaration not allowed at file scope
    int buf[bar[5]];
        ^   ~~~~~~
    1 error generated.
    

    Und wenn ich mir den Assembler Code des ersten Beispiels anschaue, scheint das array auch tatsächlich erst zur Laufzeit initialisiert zu werden.

    Wie kann ich also ein konstantes array zur Kompilierzeit mit einer constexpr Funktion initialisieren?

    Vielen Dank im Voraus!

    MfG
    Lil' huskie


  • Mod

    buf[m] = m
    

    ist kein konstanter Ausdruck.

    std::array kann (abgesehen von einfachen Kopien) nur mit einer Initialisierungsliste intialisiert werden. Also muss eine solche geschrieben werden

    template <int...> struct int_list {};
    template <int N, typename = int_list<>> struct make_int_list;
    template <int N, int... i> struct make_int_list<N, int_list<i...>> : make_int_list<N-1, int_list<0, (i+1)...>> {};
    template <int... i> struct make_int_list<0, int_list<i...>> { typedef int_list<i...> type };
    
    template <typename T> struct foo_helper;
    template <int... i> struct foo_helper<int_list<i...>>
    {
        static constexpr std::array<int, size> value = { i... };  // der Umweg, weil std::array unterspezifiziert, und brace-elision nur bei Deklarationen dieser Form möglich ist
    };
    
    template <int size>
    constexpr std::array<int, size> foo()
    {
        return foo_helper<typename make_int_list<size>::type>::value;
    }
    


  • camper schrieb:

    static constexpr std::array<int, size> value = { i... };  // der Umweg,
    // weil std::array unterspezifiziert, und brace-elision nur bei
    // Deklarationen dieser Form möglich ist
    

    In welcher Hinsicht ist std::array unterspezifiziert?
    Seit wann gibt's eigentlich "brace elision"? Ist das neu in C++11?


  • Mod

    krümelkacker schrieb:

    camper schrieb:

    static constexpr std::array<int, size> value = { i... };  // der Umweg,
    // weil std::array unterspezifiziert, und brace-elision nur bei
    // Deklarationen dieser Form möglich ist
    

    In welcher Hinsicht ist std::array unterspezifiziert?

    Weil nur garantiert wird, dass std::array wie ein Aggregat mit

    std::array<...> x = { ... };
    

    initialisiert werden kann. Was nicht spezifiziert wird, ist, wie die Daten konkret gespeichert werden, so dass nicht klar ist, wie die Klammerung ohne Auslassung auszusehen hat.

    Beispiel

    template <typename T, std::size_t N> struct array
    {
    ...
        T elems[1][1][N];
    };
    

    benötigt

    array<int,5> x = { { { { 0, 1, 2, 3 ,4 } } } };
    

    wenn nichts ausgelassen werden soll. Das wurde offenbar übersehen, als die neue Listinitialisierung in den Standard aufgenommen wurde.

    krümelkacker schrieb:

    Seit wann gibt's eigentlich "brace elision"? Ist das neu in C++11?

    Schon immer (K&R haben es erfunden).



  • camper schrieb:

    Weil nur garantiert wird, dass std::array wie ein Aggregat mit

    std::array<...> x = { ... };
    

    initialisiert werden kann. Was nicht spezifiziert wird, ist, wie die Daten konkret gespeichert werden, so dass nicht klar ist, wie die Klammerung ohne Auslassung auszusehen hat.

    Beispiel

    template <typename T, std::size_t N> struct array
    {
    ...
        T elems[1][1][N];
    };
    

    benötigt

    array<int,5> x = { { { { 0, 1, 2, 3 ,4 } } } };
    

    wenn nichts ausgelassen werden soll. Das wurde offenbar übersehen, als die neue Listinitialisierung in den Standard aufgenommen wurde.

    Ich verstehe nicht, was das mit der neuen Listeninitialisierung zu tun hat. Und ich sehe auch kein Problem darin, nicht zu wissen, ob std::array per T[N]- oder per T[1][1][N]-Member implementiert wurde.

    camper schrieb:

    krümelkacker schrieb:

    Seit wann gibt's eigentlich "brace elision"? Ist das neu in C++11?

    Schon immer (K&R haben es erfunden).

    OK. Danke.


  • Mod

    krümelkacker schrieb:

    Ich verstehe nicht, was das mit der neuen Listeninitialisierung zu tun hat. Und ich sehe auch kein Problem darin, nicht zu wissen, ob std::array per T[N]- oder per T[1][1][N]-Member implementiert wurde.

    Ohne diese Kenntnis kannst du im Allgemeinen die {}-Initialisierungsform nicht verwenden.

    struct foo
    {
        std::array<int, 5> x;
        foo() : x{0,1,2,3,4} {} // wie viele Klammern?
    };
    std::array<int, 5> bar()
    {
        return {0,1,2,3,4};  // wie viele Klammern?
    }
    
    void baz(std::array<int,5> x);
    baz({0,1,2,3,4}); // wie viele Klammern?
    


  • Ich nehme an, deine Antwort soll mir sagen, dass die Beispiele nur mit der "richtigen" Zahl von Klammerungen funktioniert und dass diese Zahl unbekannt ist. Das überrascht mich jetzt, weil ich dachte, brace elision würde hier genauso funktionieren.


  • Mod

    krümelkacker schrieb:

    Ich nehme an, deine Antwort soll mir sagen, dass die Beispiele nur mit der "richtigen" Zahl von Klammerungen funktioniert und dass diese Zahl unbekannt ist.

    Genau.

    krümelkacker schrieb:

    Das überrascht mich jetzt, weil ich dachte, brace elision würde hier genauso funktionieren.

    Leider (oder zum Glück - ich nehme an, man hat sich etwas dabei gedacht) nicht.

    n3337 8.5.1 schrieb:

    11 In a declaration of the form
    T x = { a };
    braces can be elided in an initializer-list as follows.105
    If the initializer-list begins with a left brace,
    then the succeeding comma-separated list of initializer-clauses initializes the members of a subaggregate;
    it is erroneous for there to be more initializer-clauses than members. If, however, the initializer-list for a
    subaggregate does not begin with a left brace, then only enough initializer-clauses from the list are taken
    to initialize the members of the subaggregate; any remaining initializer-clauses are left to initialize the next
    member of the aggregate of which the current subaggregate is a member. [ Example:
    float y[4][3] = {
    { 1, 3, 5 },
    { 2, 4, 6 },
    { 3, 5, 7 },
    };
    is a completely-braced initialization: 1, 3, and 5 initialize the first row of the array y[0], namely y[0][0],
    y[0][1], and y[0][2]. Likewise the next two lines initialize y[1] and y[2]. The initializer ends early and
    therefore y[3]s elements are initialized as if explicitly initialized with an expression of the form float(),
    that is, are initialized with 0.0. In the following example, braces in the initializer-list are elided; however
    the initializer-list has the same effect as the completely-braced initializer-list of the above example,
    float y[4][3] = {
    1, 3, 5, 2, 4, 6, 3, 5, 7
    };
    The initializer for y begins with a left brace, but the one for y[0] does not, therefore three elements from
    the list are used. Likewise the next three are taken successively for y[1] and y[2]. —end example ]

    ...
    105) Braces cannot be elided in other uses of list-initialization.

    array wurde ja in den Entwurf des Standards aufgenommen lange bevor die Regeln für die List-Initialisierung fertig waren. Ich vermute also, das Problem wurde einfach übersehen.


Anmelden zum Antworten