Déclaration et initialisation de tableau en C ++ 11

Voici 8 manières de déclarer et d’initialiser des tableaux en C ++ 11 qui semblent bien fonctionner sous g++ :

 /*0*/ std::array arr0({1, 2, 3}); /*1*/ std::array arr1({{1, 2, 3}}); /*2*/ std::array arr2{1, 2, 3}; /*3*/ std::array arr3{{1, 2, 3}}; /*4*/ std::array arr4 = {1, 2, 3}; /*5*/ std::array arr5 = {{1, 2, 3}}; /*6*/ std::array arr6 = std::array({1, 2, 3}); /*7*/ std::array arr7 = std::array({{1, 2, 3}}); 

Quelles sont les bonnes selon le standard ssortingct (et le futur standard C ++ 14)? Quelles sont les plus courantes / utilisées et celles à éviter (et pour quelle raison)?

C ++ 11 summary / TL; DR

  • En raison du défaut d’élision du corset, les exemples 0, 2 et 6 ne sont pas tenus de fonctionner. Les versions récentes des compilateurs implémentent toutefois la résolution proposée pour ce défaut, afin que ces exemples fonctionnent.
  • As non spécifié si std::array contient un tableau brut. Par conséquent, les exemples 1, 3, 5 et 7 ne sont pas obligés de fonctionner. Cependant, je ne connais pas d’implémentation d’une bibliothèque standard où elles ne fonctionnent pas (en pratique).
  • L’exemple 4 fonctionnera toujours: std::array arr4 = {1, 2, 3};

Je préférerais la version 4 ou la version 2 (avec le correctif d’éloignement de brace), car elles s’initialisent directement et sont nécessaires / susceptibles de fonctionner.

Pour le style AAA de Sutter, vous pouvez utiliser auto arrAAA = std::array{1, 2, 3}; , mais cela nécessite le correctif d’élision de l’attelle.


std::array doit être un agrégat [array.overview] / 2, ce qui implique qu’il n’a pas de constructeur fourni par l’utilisateur (c’est-à-dire que par défaut, copy, move et ctor).


 std::array arr0({1, 2, 3}); std::array arr1({{1, 2, 3}}); 

Une initialisation avec (..) est une initialisation directe. Cela nécessite un appel du constructeur. Dans le cas de arr0 et arr1 , seuls les constructeurs copier / déplacer sont viables. Par conséquent, ces deux exemples signifient créer un std::array temporaire à partir de la liste initialisée, et le copier / déplacer vers la destination . Grâce à l’élision copie / déplacement, le compilateur est autorisé à annuler cette opération de copie / déplacement, même si elle a des effets secondaires.

NB même si les valeurs temporaires sont des valeurs, il peut invoquer une copie (sémantiquement, avant élision de la copie) car le ctor de déplacement de std::array pourrait ne pas être déclaré implicitement, par exemple s’il était supprimé.


 std::array arr6 = std::array({1, 2, 3}); std::array arr7 = std::array({{1, 2, 3}}); 

Ce sont des exemples d’initialisation de copie. Il y a deux temporaires créés:

  • via la liste initiée {1, 2, 3} pour appeler le constructeur copier / déplacer
  • via l’expression std::array(..)

ce dernier temporaire est ensuite copié / déplacé vers la variable de destination nommée. La création des deux temporaires peut être élidée.

Autant que je sache, une implémentation pourrait écrire un explicit array(array const&) = default; constructeur et ne pas violer la norme; cela rendrait ces exemples mal formés. (Cette possibilité est exclue par [conteneur.requirements.general], félicitations à David Krauss, voir cette discussion .)


 std::array arr2{1, 2, 3}; std::array arr3{{1, 2, 3}}; std::array arr4 = {1, 2, 3}; std::array arr5 = {{1, 2, 3}}; 

Ceci est une initialisation globale. Ils initialisent tous “directement” le std::array , sans appeler un constructeur de std::array et sans créer (sémantiquement) un tableau temporaire. Les membres de std::array sont initialisés par copie-initialisation (voir ci-dessous).


Sur le sujet de la prothèse-élision:

Dans la norme C ++ 11, l’élision entre accolades ne s’applique qu’aux déclarations de la forme T x = { a }; mais pas à T x { a }; . Ceci est considéré comme un défaut et sera corrigé en C ++ 1y. Cependant, la résolution proposée ne fait pas partie du standard (statut du DRWP, voir en haut de la page liée) et vous ne pouvez donc pas compter sur votre compilateur pour le mettre en œuvre également pour T x { a }; .

Par conséquent, std::array arr2{1, 2, 3}; (exemples 0, 2, 6) sont mal formés, à proprement parler. Pour autant que je sache, les versions récentes de clang ++ et de g ++ permettent l’élision de l’accolade dans T x { a }; déjà.

Dans l’exemple 6, std::array({1, 2, 3}) utilise l’initialisation de copie: l’initialisation pour la transmission d’argument est également copy-init. Cependant, la ressortingction défectueuse de l’élision entre accolades, “dans une déclaration de la forme T x = { a }; , interdit également l’élision entre accolades, car il ne s’agit pas d’une déclaration et certainement pas de cette forme.


Au sujet de l’initialisation des agrégats:

Comme le souligne Johannes Schaub dans un commentaire , il est uniquement garanti que vous pouvez initialiser un std::array avec la syntaxe suivante [array.overview] / 2:

  tableau  a = { liste d'initialisation }; 

Vous pouvez en déduire que si accolade est autorisée sous la forme T x { a }; , que la syntaxe

  tableau  a { initializer-list }; 

est bien formé et a la même signification. Cependant, il n’est pas garanti que std::array contienne réellement un tableau brut en tant que son seul membre de données (voir aussi LWG 2310 ). Je pense qu’un exemple pourrait être une spécialisation partielle std::array , où il y a deux données membres T m0 et T m1 . Par conséquent, on ne peut pas conclure que

  tableau  a {{ initializer-list }}; 

est bien formé. Cela conduit malheureusement à la situation où il n’existe aucun moyen garanti d’initialiser une élision temporaire std::array sans attelle pour T x { a }; , et signifie également que les exemples impairs (1, 3, 5, 7) ne sont pas obligés de fonctionner.


Toutes ces méthodes pour initialiser un std::array mènent éventuellement à une initialisation d’agrégat. Il est défini comme une initialisation de copie des membres agrégés. Toutefois, l’initialisation de copie à l’aide d’une liste d’initialisation barrée peut toujours initialiser directement un membre agrégé. Par exemple:

 struct foo { foo(int); foo(foo const&)=delete; }; std::array arr0 = {1, 2}; // error: deleted copy-ctor std::array arr1 = {{1}, {2}}; // error/ill-formed, cannot initialize a // possible member array from {1} // (and too many initializers) std::array arr2 = {{{1}, {2}}}; // not guaranteed to work 

La première tente d’initialiser les éléments du tableau à partir des clauses initializer 1 et 2 , respectivement. Cette copie-initialisation est équivalente à foo arr0_0 = 1; qui à son tour équivaut à foo arr0_0 = foo(1); ce qui est illégal (copieur supprimé).

La seconde ne contient pas une liste d’expressions, mais une liste d’initialisateurs. Par conséquent, elle ne répond pas aux exigences de [array.overview] / 2. En pratique, std::array contient un membre de données de tableau brut, qui serait initialisé (uniquement) à partir de la première clause d’initialisation {1} , la seconde clause {2} est alors illégale.

Le troisième problème est le même que le second: cela fonctionne s’il existe un membre de données de tableau, mais cela n’est pas garanti.

Je crois qu’ils sont tous ssortingctement conformes, sauf éventuellement arr2 . Je choisirais la méthode arr3 , car elle est concise, claire et définitivement valable. Si arr2 est valide (je ne suis pas sûr), ce serait encore mieux, en fait.

La combinaison des parens et des accolades (0 et 1) ne me convient jamais parfaitement, les égaux (4 et 5) sont acceptables, mais je préfère la version plus courte, et les valeurs 6 et 7 sont simplement absurdement verbales.

Cependant, vous voudrez peut-être suivre un autre chemin, en suivant le style “presque toujours automatique” de Herb Sutter :

 auto arr8 = std::array{{1, 2, 3}}; 

Cette réponse lie un rapport de bogue dans lequel -Wmissing-braces n’est plus activé par défaut lors de l’utilisation de -Wall . Si vous activez -Wmissing-braces , gcc se plaindra de 0, 2, 4 et 6 (idem clang ).

L’élision des accolades est autorisée pour les instructions sous la forme T a = { ... } mais pas T a { } .

Pourquoi le comportement C ++ initializer_list pour std :: vector et std :: array est-il différent?

Voici la réponse de James McNellis :

Cependant, ces accolades supplémentaires ne peuvent être supprimées que “dans une déclaration de la forme T x = {a};” (C ++ 11 §8.5.1 / 11), c’est-à-dire lorsque l’ancien style = est utilisé. Cette règle autorisant la suppression d’accolades ne s’applique pas à l’initialisation directe de liste. Une note de bas de page se lit comme suit: “Les accolades ne peuvent pas être supprimées dans d’autres utilisations de l’initialisation de liste.”

Il existe un rapport de défaut concernant cette ressortingction: Défaut CWG # 1270. Si le projet de résolution est adopté, l’élision de support sera autorisée pour d’autres formes d’initialisation de liste, …

Si la résolution proposée est adoptée, la suppression par accolade sera autorisée pour les autres formes d’initialisation de liste, et les éléments suivants seront bien formés: std :: array y {1, 2, 3, 4};

Et la réponse de Xeo :

… tandis que std :: array n’a pas de constructeur et que la liste d’initialisation {1, 2, 3, 4} entre accolades n’est en fait pas interprétée comme une liste std :: initializer_list, mais une initialisation d’agrégat pour le tableau de style C de std :: array (d’où provient le deuxième ensemble d’accolades: un pour std :: array, un pour le tableau de membres de style C interne).

std::array n’a pas de constructeur qui prend un initializer_list . Pour cette raison, il est traité comme une initialisation globale. Si c’était le cas, cela ressemblerait à quelque chose comme ceci:

 #include  #include  struct test { int inner[3]; test(std::initializer_list list) { std::copy(list.begin(), list.end(), inner); } }; #include  int main() { test t{1, 2, 3}; test t2({1, 2, 3}); test t3 = {1, 2, 3}; test t4 = test({1, 2, 3}); for (int i = 0; i < 3; i++) std::cout << t.inner[i]; for (int i = 0; i < 3; i++) std::cout << t2.inner[i]; for (int i = 0; i < 3; i++) std::cout << t3.inner[i]; for (int i = 0; i < 3; i++) std::cout << t4.inner[i]; } 

Les 2 derniers sont redondants: vous pouvez utiliser les 6 formes de déclaration de tableau situées à droite de l’affectation. De plus, si votre compilateur n’optimise pas la copie, ces versions sont moins efficaces.

Les doubles accolades sont obligatoires avec le constructeur initializer-list, votre troisième ligne n’est donc pas valide.