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