151 votes

Pourquoi le C++11 ne prend-il pas en charge les listes d'initialisateurs désignés comme le C99 ?

Pensez-y :

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

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

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

Quel était le c++11 la raison pour laquelle le comité de normalisation a exclu le soutien d'une fonction aussi pratique ?

118voto

Jonathan Mee Points 658

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

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

Les initialisations désignées suivantes, qui sont valables en C, sont restreintes en C++ :

  • struct A a = { .y = 1, .x = 2 } n'est pas valide en C++ car les désignateurs doivent apparaître dans l'ordre de déclaration des membres de données.
  • int arr[3] = { [1] = 5 } est invalide en C++ parce que l'initialisation des tableaux désignés n'est pas supportée
  • struct B b = {.a.x = 0} n'est pas valable en C++ car les désignateurs ne peuvent pas être imbriqués.
  • struct A c = {.x = 1, 2} n'est pas valide en C++ parce que tous les membres de données ou aucun d'entre eux doivent être initialisés par des désignateurs.

Pour c++17 et plus tôt Boost a en fait soutien aux Intimidateurs désignés et il y a eu de nombreuses propositions visant à ajouter un soutien à la c++ par exemple : n4172 y La proposition de Daryle Walker d'ajouter une désignation aux initialisateurs . Les propositions citent la mise en œuvre de c99 Les initialisateurs désignés de Visual C++, gcc et Clang :

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

Mais le comité de normalisation a répété à plusieurs reprises rejette ces propositions en déclarant :

EWG a trouvé plusieurs problèmes avec l'approche proposée, et ne pense pas qu'il soit possible d'essayer de résoudre le problème, étant donné qu'il a été essayé de nombreuses fois et qu'il a échoué à chaque fois.

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

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 un initialisateur est indéterminé. [ 1 ]

(Visual C++, gcc et Clang semblent avoir un comportement convenu puisqu'ils effectuent 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 quelconque interaction, l'état du programme résultant serait également indéterminé, et le compilateur ne vous a pas averti : Existe-t-il un moyen d'être averti des initialisations désignées qui se comportent mal ?

c++ hace ont 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 entrelacée, les clauses d'initialisation, y compris celles qui résultent des expansions de paquets (17.5.3), sont évaluées dans l'ordre dans lequel elles apparaissent. C'est-à-dire que chaque calcul de valeur et chaque effect secondaire associé à une clause d'initialisation donnée est séquencé avant chaque calcul de valeur et chaque effet secondaire associé à toute clause d'initialisation qui la suit dans la liste séparée par des virgules de la liste d'initialisation.

Alors c++ aurait exigé que cela soit exécuté dans l'ordre :

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

Rupture de la compatibilité avec les versions précédentes c99 mises en œuvre.
Comme nous l'avons vu plus haut, ce problème a été contourné par les limitations imposées aux initialisateurs désignés acceptés dans le cadre de l'initiative de l'UE. c++20 . Ils fournissent un comportement standardisé, garantissant l'ordre d'exécution des initialisateurs désignés.

47voto

keebus Points 50

Un peu de piratage, donc je partage juste pour le plaisir.

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

Et utilise-le comme :

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

qui s'étend à :

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

42voto

bames53 Points 38303

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

D'autre part, la fonction d'initialisateurs désignés vise davantage à exposer les membres et à en faciliter l'accès directement dans le code client. Cela conduit à des choses comme avoir une personne âgée de 18 (ans ?) mais dont la taille et le poids sont de zéro.


En d'autres termes, les initialisateurs désignés prennent en charge un style de programmation où les éléments internes sont exposés et où le client dispose d'une certaine souplesse pour décider de la manière dont il souhaite utiliser le type.

Le C++ est plus intéressé par le fait de mettre la flexibilité du côté de la designer d'un type à la place, de sorte que les concepteurs peuvent faire en sorte qu'il soit facile d'utiliser un type correctement et difficile de l'utiliser incorrectement. Le fait de confier au concepteur le contrôle de la manière dont un type peut être initialisé fait partie de cette démarche : le concepteur détermine les constructeurs, les initialisateurs de classe, etc.

24voto

SergeyA Points 2159

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

7voto

Levi Points 1

Caractéristiques de deux noyaux du C99 que C++11 Lacks mentionne "Designated Initializers and C++".

Je pense que l'initialisateur désigné est lié à une optimisation potentielle. Ici, j'utilise "gcc/g++" 5.1 comme 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 le savions au moment de la compilation, a_point.x est nulle, on peut donc s'attendre à ce que foo est 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 l'impression 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 voici la sortie du code assembleur 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 que a_point n'est pas vraiment une valeur constante au moment de la compilation.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X