Pourquoi C ++ 11 ne prend-il pas en charge les listes d'initialiseurs désignées comme C99? [fermé]

121

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 pas en C ++ 11.

Quel était le Pourquoi le comité standard a-t-il exclu la prise en charge d'une fonctionnalité aussi pratique?

xmllmx
la source
10
Cela n'a apparemment pas de sens pour le comité de conception de l'inclure, ou cela n'a tout simplement pas été soulevé lors des réunions. Il convient de noter que les initialiseurs désignés par C99 ne sont dans aucune des versions de spécification C ++. Les constructeurs semblent être la construction d'initialisation préférée, et pour une bonne raison: ils garantissent une initialisation d'objet cohérente, si vous les écrivez correctement.
Robert Harvey
19
Votre raisonnement est en arrière, une langue n'a pas besoin d'avoir une justification pour ne pas avoir une fonctionnalité, elle a besoin d'une justification pour en avoir une et une forte en plus. Le C ++ est assez gonflé, tel quel.
Matthieu M.
42
Une bonne raison (qui ne peut pas être résolue avec les constructeurs sauf en écrivant des wrappers stupéfiants) est que, que vous utilisiez ou non C ++, la plupart des vraies API sont C, pas C ++, et peu d'entre elles vous obligent à fournir une structure dans laquelle vous voulez définir un ou deux champs - et pas nécessairement le premier - mais il faut que le reste soit initialisé à zéro. L'API Win32 en OVERLAPPEDest un exemple. Etre capable d'écrire ={.Offset=12345};rendrait le code beaucoup plus clair (et probablement moins sujet aux erreurs). Les sockets BSD sont un exemple similaire.
Damon
14
Le code en mainC99 n'est pas légal. Il devrait lire struct Person p = { .age = 18 };
chqrlie
14
FYI C ++ 20 prendra en charge les initialiseurs désignés
Andrew Tomazos

Réponses:

34

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

D'autre part, la fonctionnalité d'initialisation désignée consiste davantage à exposer et à rendre les membres faciles d'accès 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 éléments internes sont exposés, et le client a la possibilité de décider comment il souhaite utiliser le type.

C ++ est plus intéressé à mettre la flexibilité du côté du concepteur d'un type à la place, afin que les concepteurs puissent faciliter l'utilisation correcte d'un type et difficile à utiliser de manière incorrecte. Mettre le concepteur en contrôle sur la façon dont un type peut être initialisé en fait partie: le concepteur détermine les constructeurs, les initialiseurs en classe, etc.

bames53
la source
12
Veuillez montrer un lien de référence pour ce que vous dites est la raison pour laquelle C ++ n'a pas d'initialiseurs désignés. Je ne me souviens pas avoir jamais vu la proposition.
Johannes Schaub - litb le
20
N'est-ce pas la raison même de ne pas fournir de constructeur pour Personlaquelle son auteur voulait offrir le plus de flexibilité possible aux utilisateurs pour définir et initialiser les membres? L'utilisateur peut également déjà écrire Person p = { 0, 0, 18 };(et pour de bonnes raisons).
Johannes Schaub - litb
7
Quelque chose de similaire a récemment été accepté dans la spécification C ++ 14 par open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3605.html .
Johannes Schaub - litb
4
@ JohannesSchaub-litb Je ne parle pas de la cause purement mécanique et immédiate (c'est-à-dire qu'elle n'a pas été proposée au comité). Je décris ce que je crois être le facteur dominant. - Persona un design très C donc les fonctionnalités C peuvent avoir un sens. Cependant, C ++ permet probablement une meilleure conception qui évite également le besoin d'initialiseurs désignés. - À mon avis, la suppression de la restriction sur les initialiseurs en classe pour les agrégats est beaucoup plus conforme à la philosophie de C ++ que les initialiseurs désignés.
bames53 du
4
Le remplacement C ++ pour cela pourrait être nommé arguments de fonction. Mais pour le moment, les arguments de nom n'existent pas officiellement. Voir N4172 Arguments nommés pour une proposition de ceci. Cela rendrait le code moins sujet aux erreurs et plus facile à lire.
David Baird
89

Le 15 juillet 17, P0329R4 a été accepté dans lestandard: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0329r4.pdf
Cela apporte un support limité pourInitialiseurs désignés de. 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 désignateurs doivent apparaître dans l'ordre de déclaration des données membres
  • 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 désignateurs ne peuvent pas être imbriqués
  • struct A c = {.x = 1, 2} n'est pas valide en C ++ car tous ou aucun des membres de données doivent être initialisés par des désignateurs

Pour et plus tôt Boost prend en charge les initiateurs désignés et il y a eu de nombreuses propositions pour ajouter un support austandard, par exemple: n4172 et la proposition de Daryle Walker pour ajouter une désignation aux initialiseurs . Les propositions citent la mise en œuvre deLes initialiseurs désignés de Visual C ++, gcc et Clang revendiquent:

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

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

L'EWG a trouvé divers problèmes avec l'approche proposée et n'a pas pensé qu'il était possible d'essayer de résoudre le problème, car elle a été essayée à plusieurs reprises et chaque fois qu'elle a échoué

Les 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 : struct X foo = {.a = (char)f(), .b = g(), .c = h()}? Étonnamment, dans:

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 effectueront 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, l'état du programme résultant serait également indéterminé, et le compilateur ne vous avertirait pas : y a-t-il un moyen d'être averti du mauvais comportement des initialiseurs désignés?

n'ont des exigences strictes de liste d'initialiseur 11.6.4 [dcl.init.list] 4:

Dans la liste d'initialisation d'une liste d'initialisation accolée, les clauses d'initialisation, y compris celles qui résultent des extensions de pack (17.5.3), sont évaluées dans l'ordre dans lequel elles apparaissent. Autrement dit, chaque calcul de valeur et effet secondaire associé à une clause d'initialisation donnée est séquencé avant chaque calcul de valeur et effet secondaire associé à toute clause d'initialisation qui le suit dans la liste séparée par des virgules de la liste d'initialisation.

Alors le support aurait exigé que cela soit exécuté dans l'ordre:

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

Rupture de la compatibilité avec les précédents implémentations.
Comme indiqué ci-dessus, ce problème a été contourné par les limitations sur les initialiseurs désignés acceptés dans. Ils fournissent un comportement standardisé, garantissant l'ordre d'exécution des initialiseurs désignés.

Jonathan Mee
la source
3
Bien sûr, dans ce code: struct X { int c; char a; float b; }; X x = { .a = f(), .b = g(), .c = h() };l'appel à h()est effectué avant l'un f()ou l' autre g(). Si la définition de struct Xn'est pas proche, cela va être très surprenant. N'oubliez pas que les expressions d'initialisation ne doivent pas nécessairement être exemptes d'effets secondaires.
Ben Voigt
2
Bien sûr, ce n'est pas nouveau, l'initialisation d'un membre ctor a déjà ce problème, mais c'est dans la définition d'un membre de classe, donc un couplage étroit n'est pas une surprise. Et les initialiseurs désignés ne peuvent pas référencer les autres membres comme le peuvent les initialiseurs de membre ctor.
Ben Voigt
2
@MattMcNabb: Non, ce n'est pas plus extrême. Mais on s'attend à ce que le développeur implémentant le constructeur de classe connaisse l'ordre de déclaration des membres. Alors que le consommateur de la classe peut être un programmeur entièrement différent. Puisque le but est de permettre l'initialisation sans avoir à rechercher l'ordre des membres, cela semble être une faille fatale dans la proposition. Étant donné que les initialiseurs désignés ne peuvent pas référencer l'objet en cours de construction, la première impression est que les expressions d'initialisation pourraient être évaluées d'abord, dans l'ordre de désignation, puis l'initialisation des membres dans l'ordre de déclaration. Mais ...
Ben Voigt
2
@JonathanMee: Eh bien, l'autre question a répondu que ... Les initialiseurs d'agrégats C99 ne sont pas ordonnés, donc il n'y a aucune attente pour les initialiseurs désignés à commander. C ++ braced-init-lists SONT ordonnées, et la proposition d'initialiseurs désignés utilise un ordre potentiellement surprenant (vous ne pouvez pas être cohérent à la fois avec l'ordre lexical, utilisé pour toutes les listes braced-init, et l'ordre des membres, utilisé pour ctor-initializer -lists)
Ben Voigt
3
Jonathan: "Le support c ++ aurait exigé que cela soit exécuté dans l'ordre [...] Rupture de la compatibilité avec les implémentations précédentes de c99." Je ne comprends pas celui-ci, désolé. 1. Si l'ordre est indéterminé en C99, alors évidemment tout ordre réel devrait convenir, y compris tout choix arbitraire en C ++. b) Ne pas soutenir le des. les initialiseurs du tout cassent déjà un peu la compatibilité C99 encore plus ...
Sz.
34

Un peu de piratage, donc 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 $;
}()));
Keebus
la source
Neat, crée un lambda avec une variable nommée $de type T, et vous affectez ses membres directement avant de le renvoyer. Nifty. Je me demande s'il y a des problèmes de performance avec cela.
TankorSmash
1
Dans une version optimisée, vous ne voyez aucune trace du lambda ni de son appel. Tout est intégré.
keebus
1
J'adore cette réponse.
Seph Reed
6
Woah. Je ne savais même pas que $ était un nom valide.
Chris Watts
Il était pris en charge par les compilateurs C hérités et le support restait pour la compatibilité descendante.
keebus
22

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

SergeyA
la source
3
Mais notez qu'ils sont limités: en C ++, la prise en charge de l'initialisation désignée est restreinte par rapport aux fonctionnalités correspondantes en C. En C ++, les désignateurs pour les membres de données non statiques doivent être spécifiés dans l'ordre de déclaration, les désignateurs pour les éléments de tableau et les désignateurs imbriqués ne le sont pas les initialiseurs pris en charge et désignés et non désignés ne peuvent pas être mélangés dans la même liste d'initialiseurs. Cela signifie qu'en particulier, vous ne pourrez toujours pas créer facilement une table de recherche à clé enum .
Ruslan
@Ruslan: Je me demande pourquoi C ++ les a tellement restreints? Je comprends qu'il pourrait y avoir confusion quant à savoir si l'ordre dans lequel les valeurs des éléments sont évaluées et / ou écrites dans la structure correspond à l'ordre dans lequel les éléments sont spécifiés dans la liste d'initalisation, ou l'ordre dans lequel les membres apparaissent dans la structure, mais le La solution à cela serait simplement de dire que les expressions d'initialisation sont exécutées dans une séquence arbitraire, et la durée de vie de l'objet ne commence pas tant que l'initialisation n'est pas terminée (l' &opérateur renverrait l'adresse que l'objet aura pendant sa durée de vie).
supercat
5

Deux fonctionnalités principales du C99 qui C ++ 11 mentionnent «les initialiseurs désignés et C ++».

Je pense que le «initialiseur désigné» est lié à l'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 savions qu'au moment de la compilation, a_point.xc'est zéro, donc nous pouvions nous attendre à ce qu'il foosoit 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"

fooest optimisé pour imprimer x == 0uniquement.

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 d'assemblage 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   

Nous pouvons voir que ce a_pointn'est pas vraiment une valeur constante de temps de compilation.

wcy
la source
8
Essayez maintenant constexpr point(int _x,int _y):x(_x),y(_y){}. L'optimiseur de clang ++ semble également éliminer la comparaison dans votre code. Donc, ce n'est qu'un problème de QoI.
dyp
Je m'attendrais également à ce que tout l'objet a_point soit optimisé s'il avait une liaison interne. c'est à dire le mettre dans l'espace de noms anonyme et voir ce qui se passe. goo.gl/wNL0HC
Arvid
@dyp: Même la définition d'un constructeur n'est possible que si le type est sous votre contrôle. Vous ne pouvez pas faire cela, par exemple, pour struct addrinfoou struct sockaddr_in, il vous reste donc des affectations distinctes des déclarations.
musiphil
2
@musiphil Au moins en C ++ 14, ces structures de style C peuvent être correctement configurées dans une fonction constexpr en tant que variables locales à l'aide de l'affectation, puis renvoyées à partir de cette fonction. De plus, mon propos n'était pas de montrer une implémentation alternative du constructeur en C ++ qui permet l'optimisation, mais de montrer qu'il est possible pour le compilateur d'effectuer cette optimisation si la forme d'initialisation est différente. Si le compilateur est "assez bon" (c'est-à-dire prend en charge cette forme d'optimisation), alors il ne devrait pas être pertinent que vous utilisiez un ctor ou des initialiseurs désignés, ou autre chose.
dyp