2015-07-16 21 views
15

Si consideri il seguente codice:inizializzazione di std :: array <>

#include <array> 

struct A 
{ 
    int a; 
    int b; 
}; 

static std::array<A, 4> x1 = 
{ 
     { 1, 2 }, 
     { 3, 4 }, 
     { 5, 6 }, 
     { 7, 8 } 
}; 

static std::array<A, 4> x2 = 
{ 
    { 
     { 1, 2 }, 
     { 3, 4 }, 
     { 5, 6 }, 
     { 7, 8 } 
    } 
}; 

static std::array<A, 4> x3 = 
{ 
     A{ 1, 2 }, 
     A{ 3, 4 }, 
     A{ 5, 6 }, 
     A{ 7, 8 } 
}; 

static std::array<A, 4> x4 = 
{ 
     A{ 1, 2 }, 
     { 3, 4 }, 
     { 5, 6 }, 
     { 7, 8 } 
}; 

compilazione con gcc:

$ gcc -c --std=c++11 array.cpp 
array.cpp:15:1: error: too many initializers for ‘std::array<A, 4ul>’ 
}; 
^ 
$ 

NB1: Commentando la prima dichiarazione di inizializzazione, il codice viene compilato senza errori.
NB2: la conversione di tutte le inizializzazioni in chiamate di costruttore produce gli stessi risultati.
NB3: MSVC2015 si comporta allo stesso modo.

Posso capire perché la prima inizializzazione non riesce a compilare e perché la seconda e la terza sono OK. (ad esempio, vedere C++11: Correct std::array initialization?.)

La mia domanda è: perché esattamente l'inizializzazione finale viene compilata?

+0

Mi dispiace ma non riesco a capire perché il primo compito non riesce a compilare, potresti dirmi di più, per favore? È interessante ! – Telokis

+1

@Ninetainedo - vedere la domanda collegata. – Jeremy

+0

@ dyp - Corretto. – Jeremy

risposta

27

Versione corta: una clausola di inizializzazione che inizia con { interrompe brace-elision. Questo è il caso del primo esempio con {1,2}, ma non nel terzo né nel quarto che usano A{1,2}. Brace-elision consuma le successive clausole di inizializzazione N (dove N dipende dall'aggregato da inizializzare), motivo per cui solo la prima clausola di inizializzatore di N non deve iniziare con {.


In tutte le implementazioni della libreria C++ standard che conosco, std::array è una struttura che contiene una matrice C-style. Cioè, si ha un aggregato che contiene un sub-aggregati, molto simile

template<typename T, std::size_t N> 
struct array 
{ 
    T __arr[N]; // don't access this directly! 
}; 

Quando si inizializza un std::array da un rinforzato-init-list, avrete quindi bisogno di inizializzare i membri della contiene l'array. Pertanto, in tali implementazioni, la forma esplicita è:

std::array<A, 4> x = {{ {1,2}, {3,4}, {5,6}, {7,8} }}; 

Il più esterne parentesi si riferisce alla std::array struct; la seconda serie di parentesi si riferisce alla matrice in stile C nidificata.


C++ consente nell'inizializzazione di aggregazione di omettere alcune parentesi durante l'inizializzazione di aggregati nidificati. Ad esempio:

struct outer { 
    struct inner { 
     int i; 
    }; 
    inner x; 
}; 

outer e = { { 42 } }; // explicit braces 
outer o = { 42 }; // with brace-elision 

Le regole sono le seguenti (utilizzando un progetto post-N4527, che è post-C++ 14, ma C++ 11 conteneva un difetto relativa a questo comunque):

Le parentesi graffe possono essere eliminate in un elenco di inizializzazione come segue. Se il inizializzatore-list inizia con un gancio sinistro, quindi il successivo elenco separato da virgole di inizializzazione -clausole inizializza i membri di un subaggregate; è errato che ci siano più clausole di inizializzazione rispetto ai membri.Se, tuttavia, il inizializzazione-list per un subaggregate non inizia con una doppietta a sinistra, allora solo abbastanza inizializzazione-clausole dalla lista sono presi per inizializzare le membri del subaggregate; le rimanenti clausole di inizializzatore sono sinistra per inizializzare il membro successivo dell'aggregato di cui il sottogruppo corrente è un membro.

Applicando questo al primo std::array -example:

static std::array<A, 4> x1 = 
{ 
     { 1, 2 }, 
     { 3, 4 }, 
     { 5, 6 }, 
     { 7, 8 } 
}; 

questo viene interpretato come segue:

static std::array<A, 4> x1 = 
{  // x1 { 
    {  // __arr { 
    1, //  __arr[0] 
    2 //  __arr[1] 
     //  __arr[2] = {} 
     //  __arr[3] = {} 
    }  // } 

    {3,4}, // ?? 
    {5,6}, // ?? 
    ... 
};  // } 

Il primo { è preso come l'inizializzatore del std::array struct. Le clausole di inizializzatore {1,2}, {3,4} vengono quindi prese come inizializzatori dei sottoaggregati di std::array. Si noti che std::array ha un solo sottoaggregato __arr. Poiché il primo inizializzatore clausola{1,2} inizia con un {, l'eccezione brace-elisione non si verifica, e il compilatore cerca di inizializzare l'nidificata A __arr[4] array con {1,2}. Le restanti clausole di inizializzazione {3,4}, {5,6} ecc. Non si riferiscono a nessun sub-aggregato di std::array e pertanto sono illegali.

Nel terzo e quarto esempio, il primo inizializzatore clausola per la subaggregate di std::arraynon inizia con un {, quindi viene applicata l'eccezione sinalefe tutore:

static std::array<A, 4> x4 = 
{ 
     A{ 1, 2 }, // does not begin with { 
     { 3, 4 }, 
     { 5, 6 }, 
     { 7, 8 } 
}; 

Così è interpretato come segue:

static std::array<A, 4> x4 = 
    {    // x4 { 
       // __arr {  -- brace elided 
    A{ 1, 2 }, //  __arr[0] 
    { 3, 4 }, //  __arr[1] 
    { 5, 6 }, //  __arr[2] 
    { 7, 8 } //  __arr[3] 
       // }    -- brace elided 
    };   // } 

Quindi, le cause A{1,2} tutti e quattro clausole di inizializzazione da consumare per inizializzare la matrice in stile C nidificata. Se si aggiunge un altro inizializzatore:

static std::array<A, 4> x4 = 
{ 
     A{ 1, 2 }, // does not begin with { 
     { 3, 4 }, 
     { 5, 6 }, 
     { 7, 8 }, 
     X 
}; 

allora questo X sarebbero stati utilizzati per inizializzare la prossima subaggregate di std::array. Per esempio.

struct outer { 
    struct inner { 
     int a; 
     int b; 
    }; 

    inner i; 
    int c; 
}; 

outer o = 
    {  // o { 
      // i { 
    1,  //  a 
    2,  //  b 
      // } 
    3  // c 
    };  // } 

Brace-elisione consuma prossimi N-inizializzazione clausole, dove N è definito tramite il numero di inizializzatori necessari per la (sub) aggregato da inizializzare. Pertanto, importa solo se la prima di quelle clausole di inizializzazione N inizia con uno {.

Più simile al PO:

struct inner { 
    int a; 
    int b; 
}; 

struct outer { 
    struct middle { 
     inner i; 
    }; 

    middle m; 
    int c; 
}; 

outer o = 
    {    // o { 
       // m { 
    inner{1,2}, //  i 
       // } 
    3   // c 
    };    // } 

noti che brace-elisione applica ricorsivamente; possiamo anche scrivere la confusione

outer o = 
    {  // o { 
      // m { 
      //  i { 
    1,  //  a 
    2,  //  b 
      //  } 
      // } 
    3  // c 
    };  // } 

Dove si omettono sia le parentesi per o.m e o.m.i. Le prime due clausole di inizializzatore vengono utilizzate per inizializzare lo o.m.i, il rimanente inizializza o.c. Una volta inseriamo una coppia di bretelle intorno 1,2, viene interpretato come la coppia di bretelle corrispondenti a o.m:

outer o = 
    {  // o { 
    {  // m { 
      //  i { 
     1, //  a 
     2, //  b 
      //  } 
    }  // } 
    3  // c 
    };  // } 

Qui, l'inizializzatore per o.m si avvia con una {, quindi brace-elisione non si applica. L'inizializzatore per o.m.i è 1, che non inizia con un {, quindi viene applicato il blocco-elisione per o.m.i ei due iniziatori 1 e 2 vengono consumati.

+0

Risposta piacevole e completa. Grazie! – Jeremy

+0

++ voto, ottima risposta come al solito. Una domanda però: c'è già una bozza post-N4527? – Columbo

+0

@Columbo Bene, non ufficialmente. È dal repository Github, che non ne fa un progetto di lavoro ufficiale, ma * materiale di lavoro provvisorio *. – dyp