List-initialization (since C++11)
Initialise un objet à partir d'une liste d'initialisation entre accolades .
Table des matières |
Syntaxe
Initialisation directe par liste
T objet
{
arg1, arg2, ...
};
|
(1) | ||||||||
T
{
arg1, arg2, ...
}
|
(2) | ||||||||
new
T
{
arg1, arg2, ...
}
|
(3) | ||||||||
Classe
{
T membre
{
arg1, arg2, ...
}; };
|
(4) | ||||||||
Classe
::
Classe
() :
membre
{
arg1, arg2, ...
} {...
|
(5) | ||||||||
Initialisation par liste de copie
T objet
= {
arg1, arg2, ...
};
|
(6) | ||||||||
fonction
({
arg1, arg2, ...
})
|
(7) | ||||||||
return {
arg1, arg2, ...
};
|
(8) | ||||||||
objet
[{
arg1, arg2, ...
}]
|
(9) | ||||||||
objet
= {
arg1, arg2, ...
}
|
(10) | ||||||||
U
({
arg1, arg2, ...
})
|
(11) | ||||||||
Classe
{
T membre
= {
arg1, arg2, ...
}; };
|
(12) | ||||||||
L'initialisation de liste est effectuée dans les situations suivantes :
- initialisation directe par liste (les constructeurs explicites et non explicites sont pris en compte)
- copy-list-initialization (les constructeurs explicites et non explicites sont pris en compte, mais seuls les constructeurs non explicites peuvent être appelés)
operator[]
défini par l'utilisateur, où l'initialisation de liste initialise le paramètre de l'opérateur surchargé
U
dans cet exemple n'est pas le type qui est initialisé par liste ;
U
's paramètre du constructeur l'est)
Explication
Les effets de l'initialisation par liste d'un objet de type (éventuellement qualifié cv)
T
sont :
|
(depuis C++20) |
-
Si
Test une classe agrégée et que la liste d'initialisation entre accolades , qui ne contient pas de liste d'initialisation désignée, (depuis C++20) possède une seule clause d'initialisation du même type ou d'un type dérivé (éventuellement qualifié cv), l'objet est initialisé à partir de cette clause d'initialisation (par initialisation par copie pour l'initialisation de liste par copie, ou par initialisation directe pour l'initialisation de liste directe). -
Sinon, si
Test un tableau de caractères et que la liste d'initialisation entre accolades possède une seule clause d'initialisation qui est un littéral de chaîne de type approprié, le tableau est initialisé à partir du littéral de chaîne comme d'habitude .
-
Sinon, si
Test un type agrégé , l' initialisation agrégée est effectuée.
-
Sinon, si la liste d'initialisation entre accolades est vide et
Test un type de classe avec un constructeur par défaut, value-initialization est effectuée.
-
Sinon, si
Test une spécialisation de std::initializer_list , l'objet est initialisé comme décrit ci-dessous .
-
Sinon, si
Test un type classe, les constructeurs deTsont considérés, en deux phases :
-
- Tous les constructeurs qui prennent std::initializer_list comme seul argument, ou comme premier argument si les arguments restants ont des valeurs par défaut, sont examinés, et appariés par overload resolution avec un seul argument de type std::initializer_list .
-
-
Si l'étape précédente ne produit aucune correspondance, tous les constructeurs de
Tparticipent à la résolution de surcharge contre l'ensemble d'arguments constitué des clauses d'initialisation de la liste d'initialisation entre accolades, avec la restriction que seules les conversions non rétrécissantes sont autorisées. Si cette étape produit un constructeur explicite comme meilleure correspondance pour une initialisation de liste de copie, la compilation échoue (notez que dans l'initialisation de copie simple, les constructeurs explicites ne sont pas du tout considérés).
-
Si l'étape précédente ne produit aucune correspondance, tous les constructeurs de
|
(depuis C++17) |
-
Sinon (si
Tn'est pas un type classe), si la liste d'initialisation entre accolades n'a qu'une seule clause d'initialisation et que soitTn'est pas un type référence, soit est un type référence dont le type référencé est identique ou est une classe de base du type de la clause d'initialisation,Test initialisé directement (en initialisation de liste directe) ou initialisé par copie (en initialisation de liste par copie), sauf que les conversions rétrécissantes ne sont pas autorisées.
-
Sinon, si
Test un type référence qui n'est pas compatible avec le type de la clause d'initialisation :
|
(jusqu'en C++17) |
|
(depuis C++17) |
-
Sinon, si la liste d'initialisation entre accolades n'a aucune clause d'initialisation,
Test value-initialized .
Initialisation par liste de std::initializer_list
Un objet de type std:: initializer_list < E > est construit à partir d'une liste d'initialisation comme si le compilateur générait et matérialisait (depuis C++17) une prvalue de type « tableau de N const E », où N est le nombre de clauses d'initialisation dans la liste d'initialisation ; ceci est appelé le tableau de support de la liste d'initialisation.
Chaque élément du tableau de support est initialisé par copie avec la clause initialisatrice correspondante de la liste d'initialisation, et l'objet std:: initializer_list < E > est construit pour référencer ce tableau. Un constructeur ou une fonction de conversion sélectionné pour la copie doit être accessible dans le contexte de la liste d'initialisation. Si une conversion rétrécissante est requise pour initialiser l'un des éléments, le programme est mal formé.
Le tableau sous-jacent a la même durée de vie que tout autre objet temporaire , sauf que l'initialisation d'un objet std::initializer_list à partir du tableau sous-jacent étend la durée de vie du tableau exactement comme lier une référence à un temporaire .
void f(std::initializer_list<double> il); void g(float x) { f({1, x, 3}); } void h() { f({1, 2, 3}); } struct A { mutable int i; }; void q(std::initializer_list<A>); void r() { q({A{1}, A{2}, A{3}}); } // L'initialisation ci-dessus sera implémentée d'une manière approximativement équivalente à ci-dessous, // en supposant que le compilateur peut construire un objet initializer_list avec une paire de // pointeurs, et en comprenant que `__b` ne survit pas à l'appel à `f`. void g(float x) { const double __a[3] = {double{1}, double{x}, double{3}}; // tableau de support f(std::initializer_list<double>(__a, __a + 3)); } void h() { static constexpr double __b[3] = {double{1}, double{2}, double{3}}; // tableau de support f(std::initializer_list<double>(__b, __b + 3)); } void r() { const A __c[3] = {A{1}, A{2}, A{3}}; // tableau de support q(std::initializer_list<A>(__c, __c + 3)); }
Le fait que tous les tableaux de support soient distincts (c'est-à-dire stockés dans des objets non chevauchants ) n'est pas spécifié :
bool fun(std::initializer_list<int> il1, std::initializer_list<int> il2) { return il2.begin() == il1.begin() + 1; } bool overlapping = fun({1, 2, 3}, {2, 3, 4}); // le résultat n'est pas spécifié : // les tableaux arrière peuvent partager // le stockage dans {1, 2, 3, 4}
Conversions restrictives
L'initialisation de liste limite les conversions implicites autorisées en interdisant les suivantes :
- conversion d'un type à virgule flottante vers un type entier
-
conversion d'un type à virgule flottante
Tvers un autre type à virgule flottante dont le rang de conversion en virgule flottante n'est ni supérieur ni égal à celui deT, sauf lorsque le résultat de la conversion est une expression constante et qu'une des conditions suivantes est satisfaite :- La valeur convertie est finie, et la conversion ne provoque pas de dépassement de capacité.
- Les valeurs avant et après la conversion ne sont pas finies.
- conversion d'un type entier vers un type à virgule flottante, sauf lorsque la source est une expression constante dont la valeur peut être stockée exactement dans le type cible
-
conversion d'un type entier ou d'un type énumération non-scopée vers un type entier qui ne peut pas représenter toutes les valeurs de l'original, sauf lorsque
- la source est un champ de bits dont la largeur w est inférieure à celle de son type (ou, pour un type énumération , son type sous-jacent) et le type cible peut représenter toutes les valeurs d'un type entier hypothétique étendu de largeur w et avec la même représentation de signe que le type original, ou
- la source est une expression constante dont la valeur peut être stockée exactement dans le type cible
- conversion d'un type pointeur ou d'un type pointeur-vers-membre vers bool
Notes
Chaque clause d'initialisation est séquencée avant toute clause d'initialisation qui la suit dans la liste d'initialisation entre accolades. Ceci contraste avec les arguments d'une expression d'appel de fonction , qui sont non séquencés (jusqu'à C++17) séquencés de manière indéterminée (depuis C++17) .
Une liste d'initialisation entre accolades n'est pas une expression et n'a donc pas de type, par exemple decltype ( { 1 , 2 } ) est mal formé. Le fait de ne pas avoir de type implique que la déduction de type de modèle ne peut pas déduire un type correspondant à une liste d'initialisation entre accolades, donc étant donné la déclaration template < class T > void f ( T ) ; l'expression f ( { 1 , 2 , 3 } ) est mal formée. Cependant, le paramètre de modèle peut être déduit autrement, comme c'est le cas pour std:: vector < int > v ( std:: istream_iterator < int > ( std:: cin ) , { } ) , où le type d'itérateur est déduit par le premier argument mais aussi utilisé dans la position du second paramètre. Une exception spéciale est faite pour la déduction de type utilisant le mot-clé auto , qui déduit toute liste d'initialisation entre accolades comme std::initializer_list dans l'initialisation de liste par copie.
De plus, parce qu'une liste d'initialisation entre accolades n'a pas de type, des règles spéciales pour la résolution de surcharge s'appliquent lorsqu'elle est utilisée comme argument à un appel de fonction surchargé.
Les agrégats initialisent directement par copie/déplacement à partir d'une liste d'initialisation entre accolades d'une seule clause du même type, mais les non-aggrégats considèrent d'abord les constructeurs std::initializer_list :
struct X {}; // agrégat struct Q // non-agrégat { Q() = default; Q(Q const&) = default; Q(std::initializer_list<Q>) {} }; int main() { X x; X x2 = X{x}; // constructeur de copie (pas d'initialisation d'agrégat) Q q; Q q2 = Q{q}; // constructeur de liste d'initialisation (pas de constructeur de copie) }
Certains compilateurs (par exemple, gcc 10) ne considèrent que la conversion d'un pointeur ou d'un pointeur-vers-membre vers bool comme un rétrécissement en mode C++20.
| Macro de test de fonctionnalité | Valeur | Std | Fonctionnalité |
|---|---|---|---|
__cpp_initializer_lists
|
200806L
|
(C++11) | Initialisation par liste et std::initializer_list |
Exemple
#include <iostream> #include <map> #include <string> #include <vector> struct Foo { std::vector<int> mem = {1, 2, 3}; // initialisation par liste d'un membre non statique std::vector<int> mem2; Foo() : mem2{-1, -2, -3} {} // initialisation par liste d'un membre dans le constructeur }; std::pair<std::string, std::string> f(std::pair<std::string, std::string> p) { return {p.second, p.first}; // initialisation par liste dans l'instruction return } int main() { int n0{}; // initialisation-valeur (à zéro) int n1{1}; // initialisation-directe-par-liste std::string s1{'a', 'b', 'c', 'd'}; // appel du constructeur initializer-list std::string s2{s1, 2, 2}; // appel du constructeur régulier std::string s3{0x61, 'a'}; // le ctor initializer-list est préféré à (int, char) int n2 = {1}; // initialisation-copie-par-liste double d = double{1.2}; // initialisation par liste d'une prvalue, puis copy-init auto s4 = std::string{"HelloWorld"}; // idem ci-dessus, pas de temporaire // créé depuis C++17 std::map<int, std::string> m = // initialisation par liste imbriquée { {1, "a"}, {2, {'a', 'b', 'c'}}, {3, s1} }; std::cout << f({"hello", "world"}).first // initialisation par liste dans l'appel de fonction << '\n'; const int (&ar)[2] = {1, 2}; // lie une référence lvalue à un tableau temporaire int&& r1 = {1}; // lie une référence rvalue à un int temporaire // int& r2 = {2}; // erreur : impossible de lier une rvalue à une référence lvalue non const // int bad{1.0}; // erreur : conversion rétrécissante unsigned char uc1{10}; // correct // unsigned char uc2{-1}; // erreur : conversion rétrécissante Foo f; std::cout << n0 << ' ' << n1 << ' ' << n2 << '\n' << s1 << ' ' << s2 << ' ' << s3 << '\n'; for (auto p : m) std::cout << p.first << ' ' << p.second << '\n'; for (auto n : f.mem) std::cout << n << ' '; for (auto n : f.mem2) std::cout << n << ' '; std::cout << '\n'; [](...){}(d, ar, r1, uc1); // a l'effet de [[maybe_unused]] }
Sortie :
world 0 1 1 abcd cd aa 1 a 2 abc 3 abcd 1 2 3 -1 -2 -3
Rapports de défauts
Les rapports de défauts modifiant le comportement suivants ont été appliqués rétroactivement aux normes C++ précédemment publiées.
| DR | Appliqué à | Comportement publié | Comportement corrigé |
|---|---|---|---|
| CWG 1288 | C++11 |
l'initialisation par liste d'une référence avec une liste d'initialisation entre accolades d'une
seule clause liait toujours la référence à un temporaire |
lie à cette clause d'initialisation
si valide |
| CWG 1290 | C++11 | la durée de vie du tableau de support n'était pas correctement spécifiée |
spécifiée identique aux autres
objets temporaires |
| CWG 1324 | C++11 |
l'initialisation était considérée en premier pour l'initialisation à partir de
{}
|
l'initialisation d'agrégat
considérée en premier |
| CWG 1418 | C++11 | le type du tableau de support manquait de const | const ajouté |
| CWG 1467 | C++11 |
l'initialisation de même type pour les agrégats et tableaux de caractères
était interdite ; les constructeurs de liste d'initialisation avaient priorité sur les constructeurs de copie pour les listes à clause unique |
l'initialisation de même type
autorisée ; les listes à clause unique initialisent directement |
| CWG 1494 | C++11 |
lors de l'initialisation par liste d'une référence avec une clause d'initialisation d'un
type incompatible, il n'était pas spécifié si le temporaire créé était initialisé par liste directe ou par liste de copie |
cela dépend du type
d'initialisation pour la référence |
| CWG 2137 | C++11 |
les constructeurs de liste d'initialisation perdaient face aux constructeurs
de copie lors de l'initialisation par liste de
X
à partir de
{X}
|
les non-agrégats considèrent
les listes d'initialisation en premier |
| CWG 2252 | C++17 | les énumérations pouvaient être initialisées par liste à partir de valeurs non scalaires | interdit |
| CWG 2267 | C++11 |
la résolution de
CWG issue 1494
a clarifié
que les temporaires pouvaient être initialisés par liste directe |
ils sont initialisés par liste de copie
lors de l'initialisation par liste de références |
| CWG 2374 | C++17 | l'initialisation par liste directe d'une énumération autorisait trop de types sources | restreint |
| CWG 2627 | C++11 |
un champ de bits étroit d'un type entier plus grand peut être promu vers
un type entier plus petit, mais c'était toujours une conversion rétrécissante |
ce n'est pas une
conversion rétrécissante |
| CWG 2713 | C++20 |
les références vers des classes agrégées ne pouvaient pas
être initialisées par des listes d'initialisation désignées |
autorisé |
| CWG 2830 | C++11 | l'initialisation par liste n'ignorait pas la qualification cv de niveau supérieur | ignore |
| CWG 2864 | C++11 | les conversions en virgule flottante qui débordent n'étaient pas rétrécissantes | elles sont rétrécissantes |
| P1957R2 | C++11 |
la conversion d'un pointeur/pointeur-vers-membre
vers bool n'était pas rétrécissante |
considérée rétrécissante |
| P2752R3 | C++11 | les tableaux de support avec durée de vie chevauchante ne pouvaient pas se chevaucher | ils peuvent se chevaucher |