web-dev-qa-db-fra.com

Initialisation de la valeur: MSVC vs clang

#include<cstddef>

template<typename T, std::size_t N>
struct A {
    T m_a[N];
    A() : m_a{} {}
};

struct S {
    explicit S(int i=4) {}
};

int main() {
    A<S, 3> an;
}

Le code ci-dessus se compile bien avec MSVC (2017), mais échoue avec la clause 3.8.0 (Sortie de clang++ --version && clang++ -std=c++14 -Wall -pedantic main.cpp):

clang version 3.8.0 (tags/RELEASE_380/final 263969)
Target: x86_64-unknown-linux-gnu
Thread model: posix
InstalledDir: /usr/local/bin
main.cpp:6:15: error: chosen constructor is explicit in copy-initialization
    A() : m_a{} {}
              ^
main.cpp:14:13: note: in instantiation of member function 'A<S, 3>::A' requested here
    A<S, 3> an;
            ^
main.cpp:10:14: note: constructor declared here
    explicit S(int i=4) {}
             ^
main.cpp:6:15: note: in implicit initialization of array element 0 with omitted initializer
    A() : m_a{} {}
              ^
1 error generated.

clang 5.0 refuse également de compiler ceci:

<source>:6:17: error: expected member name or ';' after declaration specifiers
    A() : m_a{} {}
                ^
<source>:6:14: error: expected '('
    A() : m_a{} {}
             ^
2 errors generated.

Si j'utilise des parenthèses simples dans le constructeur As pour (c'est-à-dire A() : m_a() {}), la compilation est correcte. D'après cppreference j'aurais soupçonné que les deux doivent aboutir à la même chose (c'est-à-dire l'initialisation de la valeur). Est-ce que je manque quelque chose ou est-ce un bug dans l'un des compilateurs?

15
phimuemue

Pour m_a{}:

  • [dcl.init] /17.1 nous envoie vers [dcl.init.list] , et [dcl.init.list] /3.4 indique que nous effectuons l'initialisation de l'agrégat le m_a par [dcl.init.aggr] .

    La sémantique des initialiseurs est la suivante. [...]

    • Si l'initialiseur est un (non-parenthèses) braced-init-list ou est =braced-init-list, l'objet ou la référence est initialisé par la liste.
    • [...]

    L'initialisation de liste d'un objet ou d'une référence de type T est définie comme suit:

    • [...]
    • Sinon, si T est un agrégat, son initialisation est effectuée.
    • [...]
  • [dcl.init.aggr] /5.2 indique que nous copions-initialisons chaque élément de m_a à partir d'une liste d'initialiseur vide, c'est-à-dire {}.

    Pour un agrégat non-union, chaque élément qui n'est pas explicitement initialisé est initialisé comme suit:

    • [...]
    • Sinon, si l'élément n'est pas une référence, l'élément est initialisé par copie à partir d'une liste d'initialiseur vide ([dcl.init.list]).
    • [...]
  • Cela nous renvoie à [dcl.init] /17.1 pour l'initialisation de chaque élément, ce qui nous renvoie à nouveau à [dcl.init.list] .
  • Cette fois, nous avons frappé [dcl.init.list] /3.5 , qui indique que l'élément est initialisé en valeur.

    L'initialisation de liste d'un objet ou d'une référence de type T est définie comme suit:

    • [...]
    • Sinon, si la liste d'initialisation ne contient aucun élément et que T est un type de classe avec un constructeur par défaut, l'objet est initialisé par la valeur.
    • [...]
  • Ce qui nous amène à [dcl.init] /8.1 , qui indique que l'élément est initialisé par défaut.

    To value-initialize un objet de type T signifie:

    • si T est un type de classe (éventuellement qualifié cv) sans constructeur par défaut ([class.ctor]) ou par défaut fourni par l'utilisateur ou supprimé, l'objet est initialisé par défaut;
    • [...]
  • Which hits [dcl.init] /7.1 , qui indique que nous énumérons les constructeurs conformément à [over.match.ctor] et que nous effectuons une résolution de surcharge sur l'initialiseur ();

    To default-initialize un objet de type T signifie:

    • Si T est un type de classe (éventuellement qualifié de cv), les constructeurs sont pris en compte. Les constructeurs applicables sont énumérés ([Over.match.ctor]), et le meilleur pour le initializer() est Choisi par résolution de surcharge. Le constructeur ainsi sélectionné est appelé , Avec une liste d'arguments vide, pour initialiser l'objet.
    • [...]
  • et [over.match.ctor] dit:

    Pour l’initialisation directe ou l’initialisation par défaut qui ne se trouve pas dans le contexte De copie-initialisation , les fonctions candidates sont tous les constructeurs De la classe de l’objet en cours d’initialisation. . Pour Copie-initialisation, les fonctions candidates sont tous les constructeurs Convertisseurs de cette classe.

  • Cette initialisation par défaut est dans le contexte de l'initialisation de copie, les fonctions candidates sont donc "tous les constructeurs de conversion de cette classe".

  • Le constructeur par défaut explicite n'est pas un constructeur de conversion. En conséquence, il n'y a pas de constructeur viable. Par conséquent, la résolution de surcharge échoue et le programme est mal formé.

Pour m_a():

  • Nous avons frappé [dcl.init] /17.4 , qui indique que le tableau est initialisé en valeur.

    La sémantique des initialiseurs est la suivante. [...]

    • [...]
    • Si l'initialiseur est (), l'objet est initialisé par valeur.
    • [...]
  • Ce qui nous amène à [dcl.init] /8.3 , qui dit que chaque élément est initialisé en valeur.

    To value-initialize un objet de type T signifie:

    • [...]
    • si T est un type de tableau, chaque élément est initialisé par valeur;
    • [...]
  • Ce qui nous amène à nouveau à [dcl.init] /8.1 , puis à [dcl.init] /7.1 , et nous énumérons à nouveau les constructeurs pour [over.match.ctor] et effectuez une résolution de surcharge sur l'initialiseur ();

  • Cette fois, l'initialisation par défaut n'est pas dans le contexte de l'initialisation par copie, de sorte que les fonctions candidates sont "tous les constructeurs de la classe de l'objet en cours d'initialisation".
  • Cette fois, le constructeur par défaut explicite est est candidat et sélectionné par résolution de surcharge. Donc, le programme est bien formé.
3
T.C.

Clang est correct.

Votre confusion vient de:

D'après cppreference j'aurais soupçonné que les deux doivent aboutir à la même chose (c'est-à-dire l'initialisation de la valeur). 

Non, ils ont des effets différents. Notez les notes sur cette page:

Dans tous les cas, si la paire d'accolades vide {} est utilisée et que T est un type d'agrégat, l'initialisation de l'agrégat est effectuée à la place de l'initialisation de la valeur.

Cela signifie que lorsqu'il est initialisé avec braced-init-list, il est préférable d'effectuer l'initialisation d'agrégat pour le type d'agrégat. Avec A() : m_a{} {}, et m_a est un tableau qui appartient à type d'agrégat , puis initialisation d'agrégat est effectué à la place:

(c'est moi qui souligne)

Chaque élément de tableau direct public base, (since C++17), ou membre de classe non statique, dans l'ordre d'affichage/indice de tableau dans la définition de classe, est copy-initialized de la clause correspondante de la liste d'initialisation.

et

Si le nombre de clauses initializer est inférieur au nombre de membres and bases (since C++17) ou si la liste d'initialiseurs est complètement vide, les membres restants and bases (since C++17) sont initialisés by their default initializers, if provided in the class definition, and otherwise (since C++14) par des listes vides, conformément aux règles habituelles d'initialisation de liste (qui effectue l'initialisation de valeur pour types de classe et classes non agrégées avec constructeurs par défaut, et initialisation d'agrégat pour les agrégats). 

Cela signifie que les éléments restants, c’est-à-dire que les 3 éléments de m_a seront initialisés en copie à partir de la liste vide; pour une liste vide, le constructeur par défaut de S sera considéré, mais il est déclaré comme explicit; le copie-initialisation n'appellera pas les constructeurs explicit:

copy-list-initialization (les constructeurs explicites et non explicites sont considérés, mais seuls les constructeurs non explicites peuvent être appelés)


Par ailleurs, A() : m_a() {} effectue l’initialisation de la valeur , puis

3) si T est un type de tableau, chaque élément du tableau est initialisé en valeur;

puis

1) si T est un type de classe sans constructeur par défaut ou avec un constructeur par défaut fourni par l'utilisateur ou supprimé, l'objet est initialisé par défaut;

alors le constructeur par défaut de S est appelé pour initialiser les éléments de m_a. Que ce soit explicit ou non n'a pas d'importance pour initialisation par défaut .

10
songyuanyao

Ceci est explicitement mal formé par la norme (la question est, cependant, pourquoi?):

m_a{} list-initialise le S::m_a:

[dcl.init.list]/1

List-initialization est l'initialisation d'un objet ou d'une référence à partir d'une braced-init-list. Cet initialiseur est appelé une liste d'initialiseurs, et le _ séparé par des virgulesinitializer-clauses de la initializer-list ou selected-initializer-clauses de la selected-initializer-list sont appelés les éléments de la liste d'initialisation. Une liste d'initialisation peut être vide. L'initialisation de liste peut avoir lieu dans les contextes initialisation directe _ ou initialisation-copie; liste-initialisation dans un initialisation directe le contexte s'appelle initialisation-liste et initialisation-liste dans un copie -initialisation le contexte s'appelle initialisation de la liste de copie.

En tant que tableau, A<S, 3>::m_a est un type d'agrégat ( [dcl.init.aggr]/1 ).

[dcl.init.aggr]/3.3

  1. Lorsqu'un agrégat est initialisé par une liste d'initialiseur comme spécifié dans [dcl.init.list], [...]
    3.3 la liste des initialiseurs doit être {} et il n’existe aucun élément explicitement initialisé.

par la suite, puisque il n’existe aucun élément explicitement initialisé:

[dcl.init.aggr]/5.2

  1. Pour un agrégat non-union, chaque élément qui n'est pas explicitement initialisé est initialisé comme suit: [...]
    5.2 si l’élément n’est pas une référence, il s’agit de copie-initialisée à partir d’une liste d’initialiseurs vide ([dcl.init.list]).

Chaque S de A<S, 3>::m_a est alors copie initialisée:

[dcl.init]/17.6.3

  1. La sémantique des initialiseurs est la suivante: Le type de destination est le type de l'objet ou de la référence en cours d'initialisation et le type de source est le type de l'expression de l'initialiseur. Si l'initialiseur n'est pas une expression unique (éventuellement entre parenthèses), le type de source n'est pas défini. [...]
    17.6 Si le type de destination est un type de classe (éventuellement qualifié cv): [...]
    17.6.3 Sinon (c'est-à-dire, pour les initialisations de copie cas restants), des séquences de conversion définies par l'utilisateur qui peuvent convertir du type source en type de destination ou (lorsque une fonction de conversion est utilisée) en une classe dérivée qui est énumérée comme décrit dans [over.match.copy], et la meilleure est choisie par résolution de surcharge. Si la conversion ne peut pas être effectuée ou est ambiguë , l'initialisation est mal formée. 

Le constructeur par défaut de S étant explicite, il ne peut pas convertir le type source en type de destination} _ (S).

Par contre, la syntaxe utilisant m_a() n’est pas initialisation du membre d’agrégat et n’appelle pas initialisation par copie.

2
YSC

Si je comprends bien la norme, clang est correct.

Selon [dcl.init.aggr] /8.5.1:2

Lorsqu'un agrégat est initialisé par une liste d'initialisateurs, comme spécifié Au 8.5.4, les éléments de la liste d'initialiseurs sont considérés comme des initialiseurs Pour les membres de l'agrégat, en index croissant. ou ordre des membres. Chaque membre est copié à partir de la clause d'initialisation Correspondante.

Et plus loin dans la même clause [dcl.init.aggr] /8.5.1:7

S'il y a moins de clauses d'initialisation dans la liste que de membres Dans l'agrégat, chaque membre non initialisé explicitement Doit être initialisé à partir de son initialisation entre accolades ou égales ou, si n'est pas un initialisateur de type accolade ou égal, à partir d'une liste d'initialiseur vide

Selon les règles d’initialisation de la liste [over.match.list] /13.3.1.7

En copy-list-initialization, si un constructeur explicite est choisi, l'initialisation Est mal formée.

0
Johan