Array-Member initialisieren
-
Hi, ich habe folgendes Problem:
class base { /* viele Konstruktoren */ }; class intermediate : public base { std::array<...> member; std::array<...> foo; }; class derived : public intermediate { /* soll selbe Konstruktoren haben wie base */ };derivedhat keine Member, seine Konstruktoren sind daher trivial. Ich würde also gerne inderiveddie Konstruktoren vonbaseerben. Ich kann nur Konstruktoren der jeweils nächsten Elternklasse erben, d.h.intermediatemuss auch alle Konstruktoren vonbaseerben, damit er sie anderivedweiter vererben kann. Also:class intermediate : public base { std::array<...> member; std::array<...> foo; public: using base::base; }; class derived : public intermediate { public: using intermediate::intermediate; };Jetzt bleibt das Problem, dass die Arrays nicht initialisiert sind. Wenn ich jetzt anfange, irgendwelche Konstruktoren zu schreiben, ist die gesamte Vererbung dahin (entweder man erbt alle Konstruktoren oder garkeine
) und ich muss in derivedauch alle Konstruktoren ausschreiben. Es wäre also besser, ich könnte alle Konstruktoren schön vom Compiler generieren lassen, um Boilerplate zu sparen. Die Möglichkeit, die ich habe, ist natürlich in-class Initialization:class intermediate : public base { std::array<...> member{ }; // Value-Initialized std::array<...> foo{ ... }; // siehe unten public: using base::base; };So würde das funktionieren, aber jetzt kommt mein Problem:
Das zweite Array soll in jedem Element einen Pointer auf das jeweilige Element im ersten Array besitzen (die Arraygrößen sind Template-Parameter, es muss also alles dynamisch sein).
Ich habe mir folgendes Template-Meta-Programm dafür geschrieben:#include <array> template <typename T, int N, int... Ns> struct array_gen : public array_gen<T, N - 1, N - 1, Ns...> { }; template <typename T, int... Ns> struct array_gen<T, 0, Ns...> { static constexpr std::array<T*, sizeof...(Ns)> pointers(std::array<T, sizeof...(Ns)>& a) { return { { &a[Ns]... } }; } }; template <int N> struct intermediate { using warum_das = array_gen<int, N>; std::array<int, N> arr{ }; std::array<int*, N> a = warum_das::pointers(arr); }; int main() { intermediate<5> t; }Hier eine Frage: Warum muss ich einen Typ-Alias
warum_dasbenutzen und kann nicht direktarray_gen<int, N>::pointers(arr)schreiben? Sowohl Visual Studio 2015 als auch der GCC murren, clang frisst es. Welcher Compiler ist verbuggt?Das würde funktionieren, aber es bricht in dem Moment zusammen, wenn ich kein Array von Pointern will, sondern ein Array von
std::atomic<int*>möchte, denn Atomics können nicht kopiert werden. Was kann ich tun?
-
Kompiliert in HEAD. (Der relevante Bug-report für GCC findet sich hier)
Das wurde übrigens von CWG #325 behandelt; Die Problematik besteht darin, dass name lookup erst feststellen muss, dass
std::arrayein Template ist, sodass das Komma nicht einen neuen Deklarator (mit invalidem Initializer) einführt.Zu deiner zweiten Frage:
§8.5.1/2 schrieb:
Each member is copy-initialized from the corresponding initializer-clause.
I.e.
std::array<std::atomic<int*>> arr = {0, 0};initialisiert jedes Element via
std::atomic<int*> elem = 0;Jedoch muss für diesen Fall eine temporäre Kopie erstellt und mit dieser initialisiert werden (§8.5/(17.6.2)). Das funktioniert offensichtlich nicht. Du kannst aber stattdessen eine eigene Array-Klasse basteln, die eine
initializer_list<T>als Konstruktor-Argument nimmt und damit das Array befüllt.Also e.g. (unvollständig!):
#include <array> #include <atomic> template <typename T, std::size_t N> struct Array : std::array<T, N> { template <typename U> constexpr Array(std::initializer_list<U> ilist) { auto p = std::array<T, N>::begin(); for (auto iter = ilist.begin(); iter != ilist.end(); ++iter) *p++ = std::move(*iter); } }; int main() { int i; Array<std::atomic<int*>, 2> arr = {(int*)nullptr, &i}; }Ohne den Cast geht es übrigens nicht, da inkonsistente Typen in diesem Fall sofort in einem Fehler resultieren. Nicht besonders hübsch, aber naja.
-
Danke für deine Hilfe (und die Links).
Wenn ich das mit meinem TMP-Schnipsel verbinde, gibt's aber wieder Probleme:#include <array> #include <atomic> #include <initializer_list> template <typename T, std::size_t N> struct my_array final : std::array<T, N> { template <typename U> constexpr my_array(std::initializer_list<U> list) { auto iter = std::begin(*this); for(auto& elem : list) *iter++ = std::move(elem); } }; template <typename T, int N, int... Ns> struct array_gen : public array_gen<T, N - 1, N - 1, Ns...> { }; template <typename T, int... Ns> struct array_gen<T, 0, Ns...> { static constexpr std::initializer_list<T*> pointers(std::array<T, sizeof...(Ns)>& a) { return { &a[Ns]... }; } }; template <int N> struct test { std::array<int, N> arr{ }; my_array<std::atomic<int*>, N> a{ array_gen<int, N>::pointers(arr) }; }; int main() { test<5> t; }Jetzt wird das Template-Argument des Array-Konstruktors selbst als
initializer_listaufgelöst, so ein Blödsinn, da der Compiler sinnigerweise bei braced initialization den verfügbaren Initializer-List-Konstruktor aufruft:int main() { std::array<int, 3> arr{ }; my_array<std::atomic<int*>, 3> a{array_gen<int, 3>::pointers(arr)}; // klappt nicht my_array<std::atomic<int*>, 3> a(array_gen<int, 3>::pointers(arr)); // klappt };Im ersten Fall sieht der Compiler nur: Braced initialization, nehme
initializer_list-Konstruktor, löse Parameter nachdecltype(array_gen<int, 3>::pointers(arr))auf, im zweiten Fall löst er den Template-Parameter richtig auf. Ich kann aber nur die erste Syntax für in-class initialization nutzen und für einen Template-Konstruktor kann man keine expliziten Template-Parameter angeben. Langsam nervt mich C++ mit seinen dämlichen initializer_list/uniform initialization-Konflikten.
-
Naja, da ich dieses eigene Array eh niemals außer hier brauche, kann ich das Problem umschiffen, indem ich es auf Atomics festnagel und das Template entferne:
template <typename T, std::size_t N> struct my_array final : std::array<std::atomic<T>, N> { constexpr my_array(std::initializer_list<T> list) { auto iter = std::begin(*this); for(auto& elem : list) *iter++ = std::move(elem); } };So kompiliert es und macht, was es soll.
-
Huch, da fällt mir was viel hübscheres ein:
template <std::size_t N, typename=std::make_index_sequence<N>> struct test {}; template <std::size_t N, std::size_t... indices> struct test<N, std::index_sequence<indices...>> { std::array<int, N> arr{ }; std::array<std::atomic<int*>, N> a{{&arr[indices]}...}; };wusste gar nicht, dass
{…}ein gültiges pattern ist. Falls du noch nicht mit C++14 arbeiten kannst, lässt sichmake_index_sequenceauch leicht selbst implementieren.PS: Explizite instantiierungen gehen auch direkter:
template struct test<5>;
-
Arcoth schrieb:
Huch, da fällt mir was viel hübscheres ein:
In der Tat,
index_sequencekannte ich noch garnicht, bzw. habe es selbst gebastelt.
-
Hm, zu früh gefreut, es kompiliert nicht:
#include <array> #include <atomic> #include <utility> template <std::size_t N, typename = std::make_index_sequence<N>> struct test {}; template <std::size_t N, std::size_t... indices> struct test<N, std::index_sequence<indices...>> { std::array<int, N> arr{ }; std::array<std::atomic<int*>, N> a{ { &arr[indices] }... }; }; int main() { test<5> t; }Jedenfalls beschweren sich Clang und GCC, VS nimmt es, aber sicher nicht standardkonform. Ich muss also trotzdem mein Array von oben benutzen, also:
#include <array> #include <atomic> #include <utility> template <std::size_t N, typename = std::make_index_sequence<N>> struct test {}; template <typename T, std::size_t N> struct atomic_array : public std::array<std::atomic<T>, N> { atomic_array(std::initializer_list<T> list) { auto iter = std::begin(*this); for(auto& elem : list) *iter++ = std::move(elem); } }; template <std::size_t N, std::size_t... indices> struct test<N, std::index_sequence<indices...>> { std::array<int, N> arr{ }; atomic_array<int*, N> a{ { &arr[indices] }... }; }; int main() { test<5> t; }So geht es (endlich)!
-
Edit: Alles Quatsch. Habe lediglich einen Flüchtigkeitsfehler eingebaut

So funktionierts:std::array<std::atomic<int*>, N> a {{ { &arr[indices] }... }};
-
Warum kann ich
std::array<std::atomic<int*>, N>mit einerstd::initializer_list<std::initializer_list<int*>>initialisieren?
-
Jodocus schrieb:
Warum kann ich
std::array<std::atomic<int*>, N>mit einerstd::initializer_list<std::initializer_list<int*>>initialisieren?Weil kein entsprechender Konstruktor vorhanden ist? Du kannst lediglich Klassen mit einem
initializer_list<>Objekt initialisieren wenn ein entsprechender Konstruktor vorhanden ist. Aggregate haben keine vom User bereitgestellten Konstruktoren. Was in meinem (jetzt endlich) korrigierten Code verwendet wird, verwendetinitializer_listgar nicht.
-
Ich hab gefragt, warum ich es kann, da ich deinen Code so interpretiere (und eben array keinen solchen Konstruktor hat). Und da sind keine Initializer_lists drin? Was ist dann der Unterschied zwischen
std::array<std::atomic<int*>, N> a{ &arr[indices]... };std::array<std::atomic<int*>, N> a{{ &arr[indices]... }};und
std::array<std::atomic<int*>, N> a{{ { &arr[indices] }... }};?
Das erste ist die normale Aggregat-Initialisierung. Das zweite ist eine Aggregat-Initialisierung eines temporären Arrays, das dann kopiert wird? Und das dritte?
-
std::array<std::atomic<int*>, N> a{ &arr[indices]... };initialisiert jedes Element
en viastd::atomic<int*> e_n = &arr[n];Diese Form der initialisierung von
aist tatsächlich equivalent zu1std::array<std::atomic<int*>, N> a{{ &arr[indices]... }};Jedoch können die Klammern in diesem Fall weggelassen werden. In keinem der beiden Fälle wird ein temporäres Array oder
arrayerstellt. Es werden lediglich Temporaries durch copy-initialization benötigt. Dir ist sicherlich bekannt, dass auch instd::string s = "abc";eine Temporary erstellt wird?
Der Große Unterschied der nun in der dritten Variante ausgenutzt wird, ist dass
std::array<std::atomic<int*>, N> a{{ { &arr[indices] }... }};Eine Initialisierung a la
std::atomic<int*> e_n = {&arr[n]};impliziert - aber hier brauchen wir - dank der Regeln von list-initialization - keine Temporary, denn der Konstruktor von
en wird direkt mit&arr[n]aufgerufen.Wenn wir jedoch
std::array<std::atomic<int*>, N> a{ { &arr[indices] }... };schreiben, dann wird zuallererst versucht, dass interne Array mit
{ &arr[0] }zu initialisieren, was natürlich fehlschlägt.
---
1 Ich bin mir gerade nicht einmal sicher, ob die erste Variante standardkonform ist, da §8.5.1/11 was anderes suggeriert, aber da alle mir bekannten Implementierungen es per se annehmen, scheine ich da etwas falsch zu interpretieren
-
Arcoth schrieb:
Eine Initialisierung a la
std::atomic<int*> e_n = {&arr[n]};impliziert - aber hier brauchen wir - dank der Regeln von list-initialization - keine Temporary, denn der Konstruktor von
en wird direkt mit&arr[n]aufgerufen.Meinst du nicht eher, dass das
std::atomic<int*> e_n{&arr[n]}; // ohne =impliziert? Gemäß http://en.cppreference.com/w/cpp/language/list_initialization wäre das mit dem
=eine copy-list initialization und nicht direct.
-
Beide. In jeder Form von list-initialization werden die initializer-clauses stets direkt an den Konstruktor weitergeleitet - außer ein
initializer_listKonstruktor wird aufgerufen, was hier aber offensichtlich nicht der Fall ist.Edit: Was genau meinste gerade? Bin etwas verwirrt. Dass die Arrayelemente bei Aggregatinitialisierung stets mittels copy-initialization initialisiert werden, habe ich bereits aufgezeigt.
-
Arcoth schrieb:
Bin etwas verwirrt.
Hat sich erledigt, ich hab mir die Regeln im Standard noch mal durchgelesen. Danke!
-
Hab' auch mein vorhin erwähntes Problem gelöst:
1 Ich bin mir gerade nicht einmal sicher, ob die erste Variante standardkonform ist, da §8.5.1/11 was anderes suggeriert, aber da alle mir bekannten Implementierungen es per se annehmen, scheine ich da etwas falsch zu interpretieren
Dieser Paragraph wurde durch CWG #1270 angepasst, sodass
std::array<int, 1> arr{0};seit C++14 gültig ist.
-
Arcoth schrieb:
Edit: Was genau meinste gerade? Bin etwas verwirrt. Dass die Arrayelemente bei Aggregatinitialisierung stets mittels copy-initialization initialisiert werden, habe ich bereits aufgezeigt.
Was ich meinte, ist, dass eine List-Initialization à la
std::atomic<int*> e_n = {&arr[n]};Auch ein Temporary erzeugt, im Gegensatz zu
std::atomic<int*> e_n{&arr[n]};, wobei letzteres wohl bei
std::array<std::atomic<int*>, N> a{{ { &arr[indices] }... }};passiert.