Pourquoi std :: initializer_list n'est-il pas un langage intégré?

95

Pourquoi std::initializer_listun langage de base n'est-il pas intégré?

Il me semble que c'est une fonctionnalité assez importante de C ++ 11 et pourtant il n'a pas son propre mot-clé réservé (ou quelque chose de similaire).

Au lieu de cela, initializer_listil s'agit simplement d' une classe de modèle de la bibliothèque standard qui a un mappage implicite spécial de la nouvelle syntaxe braced-init-list {...} qui est gérée par le compilateur.

À première vue, cette solution est assez piratée .

Est-ce ainsi que les nouveaux ajouts au langage C ++ seront désormais implémentés: par les rôles implicites de certaines classes de modèles et non par le langage de base ?


Veuillez considérer ces exemples:

   widget<int> w = {1,2,3}; //this is how we want to use a class

pourquoi une nouvelle classe a-t-elle été choisie:

   widget( std::initializer_list<T> init )

au lieu d'utiliser quelque chose de similaire à l'une de ces idées:

   widget( T[] init, int length )  // (1)
   widget( T... init )             // (2)
   widget( std::vector<T> init )   // (3)
  1. un tableau classique, vous pourriez probablement ajouter constici et là
  2. trois points existent déjà dans le langage (var-args, maintenant des modèles variadiques), pourquoi ne pas réutiliser la syntaxe (et la faire sentir intégrée )
  3. juste un conteneur existant, pourrait ajouter constet&

Tous font déjà partie de la langue. Je n'ai écrit que mes 3 premières idées, je suis sûr qu'il existe de nombreuses autres approches.

emesx
la source
26
Le comité des normes déteste ajouter de nouveaux mots clés!
Alex Chamberlain
11
C'est ce que je comprends, mais il existe de nombreuses possibilités pour étendre la langue (le mot clé n'était qu'un exemple )
emesx
10
std::array<T>ne fait pas plus partie de la langue que std::initializer_list<T>. Et ce ne sont pas les seuls composants de bibliothèque sur lesquels le langage s'appuie. Voir new/ delete, type_info, divers types d'exceptions, size_tetc.
bames53
6
@Elmes: J'aurais suggéré const T(*)[N], parce que cela se comporte de manière très similaire à la façon dont std::initializer_listfonctionne.
Mooing Duck
1
Cela explique pourquoi std::arrayou un tableau de taille statique sont des alternatives moins souhaitables.
boycy

Réponses:

48

Il y avait déjà des exemples de fonctionnalités de langage «de base» qui renvoyaient des types définis dans l' stdespace de noms. typeidretourne std::type_infoet (étirant peut-être un point) sizeofretourne std::size_t.

Dans le premier cas, vous devez déjà inclure un en-tête standard pour utiliser cette fonctionnalité dite de "langage de base".

Désormais, pour les listes d'initialiseurs, il arrive qu'aucun mot clé ne soit nécessaire pour générer l'objet, la syntaxe étant des accolades sensibles au contexte. En dehors de cela, c'est la même chose que type_info. Personnellement, je ne pense pas que l'absence de mot-clé le rend "plus hacky". Un peu plus surprenant, peut-être, mais rappelez-vous que l'objectif était d'autoriser la même syntaxe d'initialisation entre accolades que celle déjà autorisée pour les agrégats.

Alors oui, vous pouvez probablement vous attendre à plus de ce principe de conception à l'avenir:

  • si plusieurs occasions se présentent où il est possible d'introduire de nouvelles fonctionnalités sans nouveaux mots-clés, le comité les prendra.
  • si de nouvelles fonctionnalités nécessitent des types complexes, ces types seront placés dans std plutôt que comme des éléments intégrés.

Par conséquent:

  • si une nouvelle fonctionnalité nécessite un type complexe et peut être introduite sans nouveaux mots-clés, vous obtiendrez ce que vous avez ici, qui est la syntaxe du "langage de base" sans nouveaux mots-clés et qui utilise les types de bibliothèque de std .

Ce à quoi cela revient, je pense, c'est qu'il n'y a pas de division absolue en C ++ entre le "langage de base" et les bibliothèques standard. Ce sont des chapitres différents dans la norme mais chacun fait référence à l'autre, et il en a toujours été ainsi.

Il existe une autre approche dans C ++ 11, à savoir que les lambdas introduisent des objets qui ont des types anonymes générés par le compilateur. Parce qu'ils n'ont aucun nom, ils ne sont pas du tout dans un espace de noms, certainement pas std. Ce n'est pas une approche appropriée pour les listes d'initialiseurs, cependant, car vous utilisez le nom de type lorsque vous écrivez le constructeur qui en accepte un.

Steve Jessop
la source
1
Il me semble que cette division n'est pas possible (mailny?) À cause de tels rôles implicites de types. type_infoet size_tsont de bons arguments .. eh bien size_tc'est juste un typedef .. alors sautons ceci. La différence entre type_infoet initializer_listest que le premier est le résultat d'un opérateur explicite et le second d'une action implicite du compilateur. Il me semble aussi que cela initializer_list pourrait être remplacé par certains conteneurs déjà existants ... ou encore mieux: tout ce que l'utilisateur déclare comme type d'argument!
emesx
4
... ou cela peut être la simple raison pour laquelle si vous avez écrit un constructeur pour vectorcela prend un arrayalors vous pouvez construire un vecteur à partir de n'importe quel tableau du bon type, et pas seulement de celui généré par la syntaxe de la liste d'initialisation. Je ne suis pas sûr que ce serait une mauvaise chose de construire des conteneurs à partir de n'importe lequel array, mais ce n'est pas l'intention du comité en introduisant la nouvelle syntaxe.
Steve Jessop
2
@Christian: Non, std::arrayn'a même pas de constructeur. Le std::arraycas est simplement une initialisation agrégée. Aussi, je vous souhaite la bienvenue à me rejoindre dans la salle de discussion Lounge <C ++>, car cette discussion devient un peu longue.
Xeo
3
@ChristianRau: Xeo signifie que les éléments sont copiés lorsque la liste d'initialisation est construite. La copie d'une liste d'initialiseurs ne copie pas les éléments contenus.
Mooing Duck
2
@Christian List-initialisation n'implique pas initializer_list. Il peut s'agir de plusieurs choses, y compris une bonne initialisation directe d'olé ou une initialisation agrégée. Aucun de ceux-ci n'implique initializer_list (et certains ne peuvent tout simplement pas fonctionner de cette façon).
R. Martinho Fernandes
42

Le Comité des normes C ++ semble préférer ne pas ajouter de nouveaux mots-clés, probablement parce que cela augmente le risque de briser le code existant (le code hérité pourrait utiliser ce mot-clé comme nom d'une variable, d'une classe ou autre).

De plus, il me semble que se définir std::initializer_listcomme un conteneur basé sur un modèle est un choix assez élégant: s'il s'agissait d'un mot-clé, comment accéderiez-vous à son type sous-jacent? Comment le feriez-vous? Vous auriez également besoin d'un tas de nouveaux opérateurs, ce qui vous obligerait simplement à vous souvenir de plus de noms et de mots-clés pour faire les mêmes choses que vous pouvez faire avec des conteneurs standard.

Traiter un std::initializer_listcomme n'importe quel autre conteneur vous donne la possibilité d'écrire du code générique qui fonctionne avec l'une de ces choses.

METTRE À JOUR:

Alors pourquoi introduire un nouveau type, au lieu d'utiliser une combinaison d'existant? (d'après les commentaires)

Pour commencer, tous les autres conteneurs ont des méthodes pour ajouter, supprimer et placer des éléments, ce qui n'est pas souhaitable pour une collection générée par le compilateur. La seule exception est std::array<>, qui encapsule un tableau de style C de taille fixe et resterait donc le seul candidat raisonnable.

Cependant, comme Nicol Bolas souligne à juste titre dans les commentaires, une autre, la différence fondamentale entre std::initializer_listet tous les autres conteneurs standard (y compris std::array<>) est que celui - ci les ont sémantique de valeur , tout std::initializer_lista la sémantique de référence . La copie d'un std::initializer_list, par exemple, ne provoquera pas de copie des éléments qu'il contient.

De plus (encore une fois, avec l'aimable autorisation de Nicol Bolas), le fait d'avoir un conteneur spécial pour les listes d'initialisation d'accolades permet de surcharger la manière dont l'utilisateur effectue l'initialisation.

Andy Prowl
la source
4
Alors pourquoi introduire un nouveau type, au lieu d'utiliser une combinaison d'existant?
emesx
3
@elmes: En fait, c'est plutôt std::array. Mais std::arrayalloue de la mémoire tout en std::initializaer_listenveloppant un tableau au moment de la compilation. Pensez-y comme la différence entre char s[] = "array";et char *s = "initializer_list";.
rodrigo
2
Et le fait d'être un type normal rend la surcharge, la spécialisation des modèles, la décoration des noms et autres, sans problèmes.
rodrigo
2
@rodrigo: std::arrayn'alloue pas de mémoire, c'est un simple T arr[N];, la même chose que le support std::initializer_list.
Xeo
6
@Xeo: T arr[N] n'alloue la mémoire, peut - être pas dans le tas dynamique , mais ailleurs ... Ainsi fait std::array. Cependant, un non-vide initializer_listne peut pas être construit par l'utilisateur donc il ne peut évidemment pas allouer de mémoire.
rodrigo
6

Cela n’a rien de nouveau. Par exemple, for (i : some_container)repose sur l' existence de méthodes spécifiques ou de fonctions autonomes dans la some_containerclasse. C # s'appuie encore plus sur ses bibliothèques .NET. En fait, je pense que c'est une solution assez élégante, car vous pouvez rendre vos classes compatibles avec certaines structures de langage sans compliquer la spécification du langage.

Effrayer
la source
2
méthodes en classe ou autonomes beginet endméthodes. C'est un peu différent de l'OMI.
emesx
3
C'est ça? Encore une fois, vous avez une construction de langage pur reposant sur une construction spécifique de votre code. Cela aurait également pu être fait en introduisant un nouveau mot-clé, par exemple,iterable class MyClass { };
Spook
mais vous pouvez placer les méthodes où vous voulez, les implémenter comme vous le souhaitez .. Il y a des similitudes, je suis d'accord! Cette question concerneinitializer_list cependant
emesx
4

Ce n'est en effet rien de nouveau et combien l'ont souligné, cette pratique était là en C ++ et est là, disons, en C #.

Andrei Alexandrescu a cependant mentionné un bon point à ce sujet: vous pouvez le considérer comme une partie de l'espace de noms imaginaire "de base", alors cela aura plus de sens.

Donc, il est en fait quelque chose comme: core::initializer_list, core::size_t, core::begin(), core::end()et ainsi de suite. C'est juste une malheureuse coïncidence que l' stdespace de noms contient des constructions de langage de base.

Artem Tokmakov
la source
2

Non seulement cela peut fonctionner complètement dans la bibliothèque standard. L'inclusion dans la bibliothèque standard ne signifie pas que le compilateur ne peut pas jouer des tours intelligents.

Bien qu'il ne puisse pas le faire dans tous les cas, il peut très bien dire: ce type est bien connu, ou un type simple, ignorons le initializer_list et avons juste une image mémoire de ce que devrait être la valeur initialisée.

En d'autres termes, cela int i {5};peut être équivalent à int i(5);ou int i=5;ou même intwrapper iw {5};Where intwrapperest une classe wrapper simple sur un int avec un constructeur trivial prenant uninitializer_list

Paul de Vrieze
la source
Avons-nous des exemples reproductibles de compilateurs jouant réellement des "trucs intelligents" comme celui-ci? Cela revient à raisonner comme si , mais j'aimerais voir une justification.
underscore_d
L'idée de l'optimisation des compilateurs est que le compilateur peut transformer le code en n'importe quel code équivalent. Le C ++ en particulier repose sur l'optimisation des abstractions "gratuites". L'idée de remplacer le code de la bibliothèque standard est courante (regardez la liste intégrée de gcc gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html ).
Paul de Vrieze
En fait, votre idée qui en int i {5}implique std::initializer_listest fausse. intn'a pas de constructeur std::initializer_list, donc le 5est juste utilisé directement pour le construire. Ainsi, l'exemple principal n'est pas pertinent; il n'y a tout simplement aucune optimisation à faire. Au-delà de cela, étant donné que std::initializer_listle compilateur crée et envoie par proxy un tableau `` imaginaire '', je suppose que cela peut favoriser l'optimisation, mais c'est la partie `` magique '' du compilateur, donc c'est différent de savoir si l'optimiseur en général peut faire quelque chose d'intelligent avec le joli objet terne contenant 2 itérateurs qui en résulte
underscore_d
1

Cela ne fait pas partie du langage de base car il peut être implémenté entièrement dans la bibliothèque, il suffit de la ligne operator new et operator delete. Quel avantage y aurait-il à rendre les compilateurs plus compliqués à intégrer?

Pete Becker
la source