Array-Member initialisieren



  • 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_list aufgelö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 nach decltype(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.


  • Mod

    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 sich make_index_sequence auch 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_sequence kannte 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)!


  • Mod

    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 einer std::initializer_list<std::initializer_list<int*>> initialisieren?


  • Mod

    Jodocus schrieb:

    Warum kann ich std::array<std::atomic<int*>, N> mit einer std::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, verwendet initializer_list gar 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?


  • Mod

    std::array<std::atomic<int*>, N> a{ &arr[indices]... };
    

    initialisiert jedes Element e n via

    std::atomic<int*> e_n = &arr[n];
    

    Diese Form der initialisierung von a ist tatsächlich equivalent zu1

    std::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 array erstellt. Es werden lediglich Temporaries durch copy-initialization benötigt. Dir ist sicherlich bekannt, dass auch in

    std::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 e n 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 e n 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.


  • Mod

    Beide. In jeder Form von list-initialization werden die initializer-clauses stets direkt an den Konstruktor weitergeleitet - außer ein initializer_list Konstruktor 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!


  • Mod

    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.


Anmelden zum Antworten