web-dev-qa-db-fra.com

Pourquoi C ++ 11 ne prend-il pas en charge les listes d’initialiseur désignées en tant que C99?

Considérer:

struct Person
{
    int height;
    int weight;
    int age;
};

int main()
{
    Person p { .age = 18 };
}

Le code ci-dessus est légal en C99, mais non légal en C++ 11.

Quelle était la justification du comité standard c ++ 11 pour exclure la prise en charge d'une fonctionnalité aussi pratique?

107
xmllmx

C++ a des constructeurs. S'il est logique d'initialiser un seul membre, cela peut être exprimé dans le programme en implémentant un constructeur approprié. C’est le type d’abstraction promue par C++.

D'autre part, la fonction d'initialisation désignée consiste davantage à exposer et à faciliter l'accès des membres directement dans le code client. Cela conduit à des choses comme avoir une personne de 18 ans (ans?) Mais avec une taille et un poids de zéro.


En d'autres termes, les initialiseurs désignés prennent en charge un style de programmation dans lequel les internes sont exposés et le client dispose d'une flexibilité lui permettant de décider de la manière dont il souhaite utiliser ce type.

C++ est plus intéressé à mettre la flexibilité du côté du concepteur d'un type, afin que les concepteurs puissent le rendre facile d'utiliser un type correctement et difficile à utiliser de manière incorrecte. Donner au concepteur le contrôle sur la manière dont un type peut être initialisé en fait partie: le concepteur détermine les constructeurs, les initialiseurs en classe, etc.

29
bames53

Le 15 juillet 17, P0329R4 a été accepté dans le c ++ 2 standard: http://www.open-std.org/jtc1/sc22/ wg21/docs/papers/2017/p0329r4.pdf
Ceci apporte un support limité pour les initialiseurs désignés de c99 . Cette limitation est décrite comme suit par C.1.7 [diff.decl] .4, étant donné:

struct A { int x, y; };
struct B { struct A a; };

Les initialisations désignées suivantes, valides en C, sont limitées en C++:

  • struct A a = { .y = 1, .x = 2 } N'est pas valide en C++ car les indicateurs doivent apparaître dans l'ordre de déclaration des membres de données.
  • int arr[3] = { [1] = 5 } N'est pas valide en C++ car l'initialisation désignée par tableau n'est pas prise en charge
  • struct B b = {.a.x = 0} N'est pas valide en C++ car les indicateurs ne peuvent pas être imbriqués
  • struct A c = {.x = 1, 2} N'est pas valide en C++ car tous les membres de la base de données, voire aucun, doivent être initialisés par les indicatifs.

Pour c ++ 17 et antérieur, Boost a en fait support pour les désignateurs désignés et de nombreuses propositions ont été faites pour ajouter un support à la norme c ++ , pour exemple: n4172 et proposition de Daryle Walker d'ajouter une désignation à des initialiseurs . Les propositions citent l'implémentation des initialiseurs désignés de c99 dans Visual C++, gcc et Clang, affirmant:

Nous pensons que les changements seront relativement simples à mettre en œuvre

Mais le comité de normalisation à plusieurs reprises rejette de telles propositions , déclarant:

Le groupe de travail électronique a trouvé divers problèmes avec l'approche proposée et ne pensait pas qu'il soit possible d'essayer de résoudre le problème, car cela a été essayé à plusieurs reprises et chaque fois qu'il a échoué.

commentaires de Ben Voigt m'ont aidé à voir les problèmes insurmontables de cette approche; donné:

struct X {
    int c;
    char a;
    float b;
};

Dans quel ordre ces fonctions seraient-elles appelées dans c99 : struct X foo = {.a = (char)f(), .b = g(), .c = h()}? Étonnamment, dans c99 :

L'ordre d'évaluation des sous-expressions dans tout initialiseur est séquencé de manière indéterminée. [ 1 ]

(Visual C++, gcc , et Clang semblent avoir un comportement convenu car ils passeront tous les appels dans cet ordre :)

  1. h()
  2. f()
  3. g()

Mais la nature indéterminée de la norme signifie que si ces fonctions avaient une interaction quelconque, l'état du programme résultant serait également indéterminé et le compilateur ne vous préviendrait pas : Y a-t-il un moyen de se mettre en garde au sujet d'un comportement erroné des initialiseurs désignés?

c ++ a-t-il des exigences strictes en matière de liste d'initialisation 11.6.4 [dcl.init.list] 4:

Dans la liste d'initialisation d'une liste d'initialisation barrée, les clauses d'initialisation, y compris celles résultant d'expansions de pack (17.5.3), sont évaluées dans l'ordre dans lequel elles apparaissent. C'est-à-dire que chaque calcul de valeur et chaque effet associé à une clause d'initialisation donnée est séquencé avant chaque calcul de valeur et d'effet secondaire associé à une clause d'initialisation qui la suit dans la liste séparée par des virgules de la liste d'initialisation.

Donc, le support de c ++ aurait dû être exécuté dans l'ordre suivant:

  1. f()
  2. g()
  3. h()

Briser la compatibilité avec les implémentations précédentes de c99 .
Comme indiqué ci-dessus, ce problème a été contourné par les limitations relatives aux initialiseurs désignés acceptées dans c ++ 2 . Ils fournissent un comportement normalisé, garantissant l'ordre d'exécution des initialiseurs désignés.

75
Jonathan Mee

Les initialisateurs désignés sont actuellement inclus dans le corpus de travail C++ 20: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0329r4.pdf pourrait enfin les voir!

22
SergeyA

Un peu de hackery, alors juste partager pour le plaisir.

#define with(T, ...)\
    ([&]{ T ${}; __VA_ARGS__; return $; }())

Et utilisez-le comme:

MyFunction(with(Params,
    $.Name = "Foo Bar",
    $.Age  = 18
));

qui s'étend à:

MyFunction(([&] {
 Params ${};
 $.Name = "Foo Bar", $.Age = 18;
 return $;
}()));
21
keebus

Deux fonctionnalités de base du C99 que C++ 11 Lacks mentionne “Initiateurs désignés et C++”.

Je pense que l’initialisateur désigné est associé à une optimisation potentielle. Ici, j'utilise "gcc/g ++" 5.1 à titre d'exemple.

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>    
struct point {
    int x;
    int y;
};
const struct point a_point = {.x = 0, .y = 0};
int foo() {
    if(a_point.x == 0){
        printf("x == 0");
        return 0;
    }else{
        printf("x == 1");
        return 1;
    }
}
int main(int argc, char *argv[])
{
    return foo();
}

Nous savions au moment de la compilation, a_point.x est égal à zéro, nous pourrions donc nous attendre à ce que foo soit optimisé en un seul printf.

$ gcc -O3 a.c
$ gdb a.out
(gdb) disassemble foo
Dump of assembler code for function foo:
   0x00000000004004f0 <+0>: sub    $0x8,%rsp
   0x00000000004004f4 <+4>: mov    $0x4005bc,%edi
   0x00000000004004f9 <+9>: xor    %eax,%eax
   0x00000000004004fb <+11>:    callq  0x4003a0 <printf@plt>
   0x0000000000400500 <+16>:    xor    %eax,%eax
   0x0000000000400502 <+18>:    add    $0x8,%rsp
   0x0000000000400506 <+22>:    retq   
End of assembler dump.
(gdb) x /s 0x4005bc
0x4005bc:   "x == 0"

foo est optimisé pour imprimer x == 0 seulement.

Pour la version C++,

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
struct point {
    point(int _x,int _y):x(_x),y(_y){}
    int x;
    int y;
};
const struct point a_point(0,0);
int foo() {
    if(a_point.x == 0){
        printf("x == 0");
        return 0;
    }else{
        printf("x == 1");
        return 1;
    }
}
int main(int argc, char *argv[])
{
    return foo();
}

Et ceci est la sortie du code assemblé optimisé.

g++ -O3 a.cc
$ gdb a.out
(gdb) disassemble foo
Dump of assembler code for function _Z3foov:
0x00000000004005c0 <+0>:    Push   %rbx
0x00000000004005c1 <+1>:    mov    0x200489(%rip),%ebx        # 0x600a50 <_ZL7a_point>
0x00000000004005c7 <+7>:    test   %ebx,%ebx
0x00000000004005c9 <+9>:    je     0x4005e0 <_Z3foov+32>
0x00000000004005cb <+11>:   mov    $0x1,%ebx
0x00000000004005d0 <+16>:   mov    $0x4006a3,%edi
0x00000000004005d5 <+21>:   xor    %eax,%eax
0x00000000004005d7 <+23>:   callq  0x400460 <printf@plt>
0x00000000004005dc <+28>:   mov    %ebx,%eax
0x00000000004005de <+30>:   pop    %rbx
0x00000000004005df <+31>:   retq   
0x00000000004005e0 <+32>:   mov    $0x40069c,%edi
0x00000000004005e5 <+37>:   xor    %eax,%eax
0x00000000004005e7 <+39>:   callq  0x400460 <printf@plt>
0x00000000004005ec <+44>:   mov    %ebx,%eax
0x00000000004005ee <+46>:   pop    %rbx
0x00000000004005ef <+47>:   retq   

On peut voir ça a_point n'est pas vraiment une valeur constante de compilation.

5
wcy