Namespaces
Variants

List-initialization (since C++11)

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

Initialise un objet à partir d'une liste d'initialisation entre accolades .

Table des matières

Syntaxe

Initialisation directe par liste

T objet { arg1, arg2, ... };

T objet {. des1 = arg1 , . des2 { arg2 } ... };

(depuis C++20)
(1)
T { arg1, arg2, ... }

T {. des1 = arg1 , . des2 { arg2 } ... }

(depuis C++20)
(2)
new T { arg1, arg2, ... }

new T {. des1 = arg1 , . des2 { arg2 } ... }

(depuis C++20)
(3)
Classe { T membre { arg1, arg2, ... }; };

Classe { T membre {. des1 = arg1 , . des2 { arg2 } ... }; };

(depuis C++20)
(4)
Classe :: Classe () : membre { arg1, arg2, ... } {...

Classe :: Classe () : membre {. des1 = arg1 , . des2 { arg2 } ... } {...

(depuis C++20)
(5)

Initialisation par liste de copie

T objet = { arg1, arg2, ... };

T objet = {. des1 = arg1 , . des2 { arg2 } ... };

(depuis C++20)
(6)
fonction ({ arg1, arg2, ... })

fonction ({. des1 = arg1 , . des2 { arg2 } ... })

(depuis C++20)
(7)
return { arg1, arg2, ... };

return {. des1 = arg1 , . des2 { arg2 } ... };

(depuis C++20)
(8)
objet [{ arg1, arg2, ... }]

objet [{. des1 = arg1 , . des2 { arg2 } ... }]

(depuis C++20)
(9)
objet = { arg1, arg2, ... }

objet = {. des1 = arg1 , . des2 { arg2 } ... }

(depuis C++20)
(10)
U ({ arg1, arg2, ... })

U ({. des1 = arg1 , . des2 { arg2 } ... })

(depuis C++20)
(11)
Classe { T membre = { arg1, arg2, ... }; };

Classe { T membre = {. des1 = arg1 , . des2 { arg2 } ... }; };

(depuis C++20)
(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)
1) initialisation d'une variable nommée avec une liste d'initialisation entre accolades
2) initialisation d'un temporaire sans nom avec une liste d'initialisation entre accolades
3) initialisation d'un objet avec une durée de stockage dynamique avec une new-expression , où l'initialiseur est une liste d'initialisation entre accolades
4) dans un initialiseur de data member non statique qui n'utilise pas le signe égal
5) dans une member initializer list d'un constructeur si une liste d'initialisation entre accolades est utilisée
  • copy-list-initialization (les constructeurs explicites et non explicites sont pris en compte, mais seuls les constructeurs non explicites peuvent être appelés)
6) initialisation d'une variable nommée avec une liste d'initialisation entre accolades après un signe égal
7) dans une expression d'appel de fonction, avec une liste d'initialisation entre accolades utilisée comme argument et l'initialisation de liste initialise le paramètre de fonction
8) dans une return instruction avec une liste d'initialisation entre accolades utilisée comme expression de retour et l'initialisation de liste initialise l'objet retourné
9) dans une expression d'indice avec un operator[] défini par l'utilisateur, où l'initialisation de liste initialise le paramètre de l'opérateur surchargé
10) dans une expression d'assignation , où l'initialisation de liste initialise le paramètre de l'opérateur surchargé
11) expression de cast fonctionnel ou autres appels de constructeur, où une liste d'initialisation entre accolades est utilisée à la place d'un argument de constructeur. L'initialisation par copie de liste initialise le paramètre du constructeur (note : le type U dans cet exemple n'est pas le type qui est initialisé par liste ; U 's paramètre du constructeur l'est)
12) dans un initialiseur de data member non statique qui utilise le signe égal

Explication

Les effets de l'initialisation par liste d'un objet de type (éventuellement qualifié cv) T sont :

  • Si la liste d'initialisation entre accolades contient une liste d'initialisation désignée et T n'est pas un type référence, T doit être une classe agrégat. Les identifiants ordonnés dans les désignateurs de la liste d'initialisation désignée doivent former une sous-séquence des identifiants ordonnés dans les membres de données non statiques directs de T . L'initialisation d'agrégat est effectuée.
(depuis C++20)
  • Si T est 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 T est 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 la liste d'initialisation entre accolades est vide et T est un type de classe avec un constructeur par défaut, value-initialization est effectuée.
  • Sinon, si T est un type classe, les constructeurs de T sont considérés, en deux phases :
  • Si l'étape précédente ne produit aucune correspondance, tous les constructeurs de T participent à 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).
  • Sinon, si T est un type énumération avec un type sous-jacent fixe U , la liste d'initialisation entre accolades n'a qu'un seul initialiseur v , et toutes les conditions suivantes sont satisfaites, alors l'énumération est initialisée avec le résultat de la conversion de v vers U :
    • L'initialisation est une initialisation de liste directe.
    • v est de type scalaire .
    • v est implicitement convertible en U .
    • La conversion de v vers U n'est pas un rétrécissement.
(depuis C++17)
  • Sinon (si T n'est pas un type classe), si la liste d'initialisation entre accolades n'a qu'une seule clause d'initialisation et que soit T n'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, T est 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 T est un type référence qui n'est pas compatible avec le type de la clause d'initialisation :
  • un temporaire prvalue du type référencé par T est initialisé par copie avec liste, et la référence est liée à ce temporaire (cela échoue si la référence est une référence lvalue non-constante).
(jusqu'en C++17)
  • une prvalue est générée. La prvalue initialise son objet résultat par initialisation par copie avec liste. La prvalue est ensuite utilisée pour initialiser directement la référence (cela échoue si la référence est une référence lvalue non-constante). Le type du temporaire est le type référencé par T , sauf si T est « référence vers un tableau de taille inconnue de U », auquel cas le type du temporaire est le type de x dans la déclaration U x [ ] H , où H est la liste d'initialisation (depuis C++20) .
(depuis C++17)
  • Sinon, si la liste d'initialisation entre accolades n'a aucune clause d'initialisation, T est 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 T vers un autre type à virgule flottante dont le rang de conversion en virgule flottante n'est ni supérieur ni égal à celui de T , 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

Voir aussi