Come emulare l’inizializzazione dell’array C “int arr = {e1, e2, e3, …}” comportamento con std :: array?

(Nota: questa domanda riguarda non dover specificare il numero di elementi e consentire comunque l’inizializzazione diretta dei tipi nidificati).
Questa domanda discute gli usi lasciati per un array C come int arr[20]; . Sulla sua risposta , @James Kanze mostra una delle ultime roccaforti di array C, le sue caratteristiche di inizializzazione uniche:

 int arr[] = { 1, 3, 3, 7, 0, 4, 2, 0, 3, 1, 4, 1, 5, 9 }; 

Non dobbiamo specificare il numero di elementi, evviva! Ora esegui iterazioni su di esso con le funzioni di C ++ 11 std::begin e std::end from ( o le tue varianti ) e non hai mai nemmeno bisogno di pensare alle sue dimensioni.

Ora, ci sono dei modi (forse TMP) per ottenere lo stesso con std::array ? L’uso di macro ha permesso di renderlo più bello. 🙂

 ??? std_array = { "here", "be", "elements" }; 

Modifica : la versione intermedia, compilata da varie risposte, assomiglia a questo:

 #include  #include  template<class T, class... Tail, class Elem = typename std::decay::type> std::array make_array(T&& head, Tail&&... values) { return { std::forward(head), std::forward(values)... }; } // in code auto std_array = make_array(1,2,3,4,5); 

E impiega tutti i tipi di roba C ++ 11:

    • Modelli Variadic
    • sizeof...
    • riferimenti di valore
    • inoltro perfetto
    • std::array , ovviamente
    • inizializzazione uniforms
    • omettendo il tipo di ritorno con inizializzazione uniforms
    • tipo di inferenza ( auto )

    E un esempio può essere trovato qui .

    Tuttavia , come sottolinea @Johannes nel commento sulla risposta di @ Xaade, non è ansible inizializzare i tipi annidati con tale funzione. Esempio:

     struct A{ int a; int b; }; // C syntax A arr[] = { {1,2}, {3,4} }; // using std::array ??? std_array = { {1,2}, {3,4} }; 

    Inoltre, il numero di inizializzatori è limitato al numero di argomenti di funzioni e modelli supportati dall’implementazione.

    Il meglio che posso pensare è:

     template auto make_array(T head, Tail... tail) -> std::array { std::array a = { head, tail ... }; return a; } auto a = make_array(1, 2, 3); 

    Tuttavia, ciò richiede che il compilatore esegua NRVO, quindi salta anche la copia del valore restituito (che è anche legale ma non obbligatorio). In pratica, mi aspetto che qualsiasi compilatore C ++ sia in grado di ottimizzarlo in modo tale che sia veloce quanto l’inizializzazione diretta.

    Mi aspetterei un semplice make_array .

     template std::array make_array(T&&... refs) { return std::array{ { std::forward(refs)... } }; } 

    Combinando alcune idee dei post precedenti, ecco una soluzione che funziona anche per costruzioni annidate (testate in GCC4.6):

     template  std::array make_array(T && t, Args &&... args) { static_assert(all_same::value, "make_array() requires all arguments to be of the same type."); // edited in return std::array{ std::forward(t), std::forward(args)...}; } 

    Stranamente, can non può rendere il valore di ritorno un riferimento di valore, che non avrebbe funzionato per le costruzioni annidate. Ad ogni modo, ecco un test:

     auto q = make_array(make_array(make_array(std::string("Cat1"), std::string("Dog1")), make_array(std::string("Mouse1"), std::string("Rat1"))), make_array(make_array(std::string("Cat2"), std::string("Dog2")), make_array(std::string("Mouse2"), std::string("Rat2"))), make_array(make_array(std::string("Cat3"), std::string("Dog3")), make_array(std::string("Mouse3"), std::string("Rat3"))), make_array(make_array(std::string("Cat4"), std::string("Dog4")), make_array(std::string("Mouse4"), std::string("Rat4"))) ); std::cout << q << std::endl; // produces: [[[Cat1, Dog1], [Mouse1, Rat1]], [[Cat2, Dog2], [Mouse2, Rat2]], [[Cat3, Dog3], [Mouse3, Rat3]], [[Cat4, Dog4], [Mouse4, Rat4]]] 

    (Per l'ultima uscita sto usando la mia bella stampante .)


    In realtà, miglioriamo la sicurezza del tipo di questa costruzione. Sicuramente abbiamo bisogno che tutti i tipi siano uguali. Un modo è quello di aggiungere un'asserzione statica, che ho modificato in precedenza. L'altro modo è di abilitare make_array solo quando i tipi sono gli stessi, in questo modo:

     template  typename std::enable_if::value, std::array>::type make_array(T && t, Args &&... args) { return std::array { std::forward(t), std::forward(args)...}; } 

    Ad ogni modo, avrai bisogno del tipo variadic all_same tipo. Eccolo, generalizzando da std::is_same (nota che il decadimento è importante per consentire il mix di T , T& , T const & via):

     template  struct all_same { static const bool value = false; }; template  struct all_same { static const bool value = std::is_same::type, typename std::decay::type>::value && all_same::value; }; template  struct all_same { static const bool value = std::is_same::type, typename std::decay::type>::value; }; template  struct all_same { static const bool value = true; }; 

    Nota che make_array() restituisce da copia-di-temporanea, che il compilatore (con sufficienti flag di ottimizzazione!) È autorizzato a trattare come un valore rvalore o comunque ottimizzare via, e std::array è un tipo aggregato, quindi il compilatore è libero scegliere il miglior metodo di costruzione ansible.

    Infine, si noti che non è ansible evitare la costruzione di copia / spostamento quando make_array imposta l'inizializzatore. Quindi std::array x{Foo(1), Foo(2)}; non ha copia / sposta, ma auto x = make_array(Foo(1), Foo(2)); ha due copie / mosse mentre gli argomenti vengono inoltrati a make_array . Non penso che tu possa migliorare su questo, perché non puoi passare lessicamente una lista di inizializzazione variadica all'helper e dedurre tipo e dimensione - se il preprocessore avesse una dimensione di sizeof... funzione per argomenti variadici, forse quello potrebbe essere fatto, ma non all'interno del linguaggio principale.

    La syntax di ritorno finale make_array può essere ulteriormente semplificata

     #include  #include  #include  template  auto make_array(T&&... t) -> std::array, sizeof...(t)> { return {std::forward(t)...}; } int main() { auto arr = make_array(1, 2, 3, 4, 5); return 0; } 

    Sfortunatamente per le classi aggregate richiede specifiche di tipo esplicite

     /* struct Foo { int a, b; }; */ auto arr = make_array(Foo{1, 2}, Foo{3, 4}, Foo{5, 6}); 

    In realtà questa implementazione make_array è elencata in sizeof … operatore


    versione c ++ 17

    Grazie alla deduzione degli argomenti del modello per la proposta di modelli di class , possiamo utilizzare le guide alla detrazione per sbarazzarci make_array

     #include  namespace std { template  array(T... t) -> array, sizeof...(t)>; } int main() { std::array a{1, 2, 3, 4}; return 0; } 

    Compilato con -std=c++1z sotto x86-64 gcc 7.0

    C ++ 11 supporterà questo modo di inizializzazione per (la maggior parte?) Contenitori std.

    (Soluzione di @dyp)

    Nota: richiede C ++ 14 ( std::index_sequence ). Sebbene si possa implementare std::index_sequence in C ++ 11.

     #include  // --- #include  #include  template  using c_array = T[]; template constexpr auto make_array(T (&&src)[N], std::index_sequence) { return std::array{{ std::move(src[Indices])... }}; } template constexpr auto make_array(T (&&src)[N]) { return make_array(std::move(src), std::make_index_sequence{}); } // --- struct Point { int x, y; }; std::ostream& operator<< (std::ostream& os, const Point& p) { return os << "(" << px << "," << py << ")"; } int main() { auto xs = make_array(c_array{{1,2}, {3,4}, {5,6}, {7,8}}); for (auto&& x : xs) { std::cout << x << std::endl; } return 0; } 

    So che è passato un po ‘di tempo da quando è stata posta questa domanda, ma ritengo che le risposte esistenti presentino ancora alcune lacune, quindi mi piacerebbe proporre la mia versione leggermente modificata. Di seguito sono riportati i punti che ritengo mancano alcune risposte esistenti.


    1. Non è necessario affidarsi a RVO

    Alcune risposte dicono che dobbiamo affidarci a RVO per restituire l’ array costruito. Quello non è vero; possiamo usare l’ inizializzazione della copia-lista per garantire che non ci saranno mai dei provvisori creati. Quindi, invece di:

     return std::array{values}; 

    dovremmo fare:

     return {{values}}; 

    2. Rendi make_array una funzione di constexpr

    Questo ci consente di creare array costanti in fase di compilazione.

    3. Non è necessario verificare che tutti gli argomenti siano dello stesso tipo

    Prima di tutto, se non lo sono, il compilatore emetterà comunque un avviso o un errore perché l’inizializzazione delle liste non consente il restringimento. In secondo luogo, anche se decidessimo veramente di fare la nostra static_assert (forse per fornire un messaggio di errore migliore), dovremmo comunque confrontare i tipi decaduti degli argomenti piuttosto che i tipi non static_assert . Per esempio,

     volatile int a = 0; const int& b = 1; int&& c = 2; auto arr = make_array(a, b, c); // Will this work? 

    Se stiamo semplicemente static_assert che a , b , c hanno lo stesso tipo, allora questo controllo fallirà, ma probabilmente non è quello che ci aspetteremmo. Invece, dovremmo confrontare i loro tipi std::decay_t (che sono tutti int s)).

    4. Deduce il tipo di valore dell’array decadendo gli argomenti inoltrati

    Questo è simile al punto 3. Utilizzando lo stesso snippet di codice, ma non specificare il tipo di valore esplicitamente questa volta:

     volatile int a = 0; const int& b = 1; int&& c = 2; auto arr = make_array(a, b, c); // Will this work? 

    Probabilmente vorremmo creare un array , ma le implementazioni nelle risposte esistenti probabilmente non riescono a farlo. Quello che possiamo fare è, invece di restituire uno std::array , restituire uno std::array, …> .

    C’è uno svantaggio su questo approccio: non possiamo più restituire una array di tipo valore qualificato cv. Ma la maggior parte delle volte, invece di qualcosa come un array , const array un const array . C’è un compromesso, ma penso che sia ragionevole. Anche il C ++ 17 std::make_optional adotta questo approccio:

     template< class T > constexpr std::optional> make_optional( T&& value ); 

    Prendendo in considerazione i suddetti punti, una piena implementazione funzionante di make_array in C ++ 14 si presenta così:

     #include  #include  #include  template constexpr std::array, 1 + sizeof... (Ts)> make_array(T&& t, Ts&&... ts) noexcept(noexcept(std::is_nothrow_constructible< std::array, 1 + sizeof... (Ts)>, T&&, Ts&&... >::value)) { return {{std::forward(t), std::forward(ts)...}}; } template constexpr std::array_t, 0> make_array() noexcept { return {}; } 

    Uso:

     constexpr auto arr = make_array(make_array(1, 2), make_array(3, 4)); static_assert(arr[1][1] == 4, "!"); 

    Se std :: array non è un vincolo e se hai Boost, dai un’occhiata a list_of() . Questo non è esattamente come l’inizializzazione dell’array di tipo C che si desidera. Ma vicino.

    Crea un tipo di creatore di array.

    Sovraccarica l’ operator, per generare un modello di espressione concatenando ogni elemento ai precedenti tramite riferimenti.

    Aggiungi una funzione di finish libera che prende il creatore di array e genera una matrice direttamente dalla catena di riferimenti.

    La syntax dovrebbe essere simile a questa:

     auto arr = finish( make_array->* 1,2,3,4,5 ); 

    Non consente la costruzione basata su {} , come fa solo l’ operator= . Se sei disposto a usare = possiamo farlo funzionare:

     auto arr = finish( make_array= {1}={2}={3}={4}={5} ); 

    o

     auto arr = finish( make_array[{1}][{2}[]{3}][{4}][{5}] ); 

    Nessuna di queste sembra una buona soluzione.

    L’uso di variardics limita il limite imposto dal compilatore al numero di vararg e blocca l’uso ricorsivo di {} per le sottostrutture.

    Alla fine, non c’è davvero una buona soluzione.

    Quello che faccio è che scrivo il mio codice in modo da consumare in modo agnostico sia i dati T[] che std::array – non interessa a chi lo nutro. A volte questo significa che il mio codice di inoltro deve trasformare con attenzione gli array [] in std::array trasparente.