Pourquoi dois-je écrire explicitement le mot-clé ‘auto’?

Je passe à C ++ 11 de C ++ 98 et je me suis familiarisé avec le mot auto clé auto . Je me demandais pourquoi nous devions déclarer explicitement auto si le compilateur est capable de déduire automatiquement le type. Je sais que C ++ est un langage fortement typé et que c’est une règle mais n’est-il pas possible d’atteindre le même résultat sans déclarer explicitement une variable auto ?

Supprimer l’ auto explicite briserait la langue:

par exemple

 int main() { int n; { auto n = 0; // this shadows the outer n. } } 

où vous pouvez voir que laisser tomber l’ auto ne serait pas l’ ombre de l’extérieur n .

Votre question permet deux interprétations:

  • Pourquoi avons-nous besoin de l’auto? Ne pouvons-nous pas simplement le laisser tomber?
  • Pourquoi sums-nous obligés d’utiliser l’auto? Ne pouvons-nous pas simplement l’impliquer, si ce n’est pas donné?

Bathsheba a répondu gentiment à la première interprétation, pour la seconde, considérons ce qui suit (en supposant qu’il n’existe pas d’autres déclarations jusqu’à présent; C ++ hypothétiquement valide):

 int f(); double g(); n = f(); // declares a new variable, type is int; d = g(); // another new variable, type is double if(n == d) { n = 7; // reassigns n auto d = 2.0; // new d, shadowing the outer one } 

Ce serait possible, les autres langages se débrouillent très bien avec (enfin, à part peut-être le problème de l’ombrage) … Ce n’est pas le cas en C ++, et la question est maintenant: ?

Cette fois, la réponse n’est pas aussi évidente que dans la première interprétation. Une chose est évidente: l’exigence explicite du mot clé rend le langage plus sûr (je ne sais pas si c’est ce qui a poussé le comité linguistique à prendre sa décision, mais cela rest un point):

 grummel = f(); // ... if(true) { brummel = f(); //^ uh, oh, a typo... } 

Pouvons-nous nous entendre sur le fait que cela ne nécessite aucune explication supplémentaire?

Le danger encore plus grand de ne pas exiger d’auto, c’est que cela signifie que l’ajout d’une variable globale dans un endroit éloigné d’une fonction (par exemple dans un fichier d’en-tête) peut transformer ce qui était censé être une déclaration locale. scoped variable dans cette fonction dans une affectation à la variable globale … avec des conséquences potentiellement désastreuses (et certainement très déroutantes).

(commentaire de psmears cité en raison de son importance – merci de faire allusion à)

N’était-il pas possible d’atteindre le même résultat sans déclarer explicitement une variable auto ?

Je vais reformuler votre question légèrement pour vous aider à comprendre pourquoi vous avez besoin d’ auto :

N’at-il pas été possible d’atteindre le même résultat sans utiliser explicitement un type placeholder ?

N’était-ce pas possible ? Bien sûr, c’était “possible”. La question est de savoir si cela en vaut la peine.

La plupart des syntaxes dans d’autres langages qui ne font pas de noms de machine fonctionnent de deux manières. Il y a la manière Go-like, où name := value; déclare une variable. Et il y a la manière de Python, où name = value; déclare une nouvelle variable si name n’a pas encore été déclaré.

Supposons qu’il n’y ait pas de problèmes de syntaxe lors de l’application de la syntaxe à C ++ (même si je peux déjà voir cet identifier suivi de : en C ++ signifie «créer une étiquette»). Alors, qu’est-ce que vous perdez par rapport aux espaces réservés?

Eh bien, je ne peux plus faire ça:

 auto &name = get<0>(some_tuple); 

Voir, auto signifie toujours “valeur”. Si vous voulez obtenir une référence, vous devez utiliser explicitement un & . Et cela échouera à juste titre si l’expression d’affectation est une valeur. Aucune des syntaxes basées sur l’affectation ne permet de différencier les références et les valeurs.

Maintenant, vous pouvez faire de telles syntaxes d’affectation déduire des références si la valeur donnée est une référence. Mais cela voudrait dire que vous ne pouvez pas faire:

 auto name = get<0>(some_tuple); 

Ceci copie du tuple, créant un object indépendant de some_tuple . Parfois, c’est exactement ce que vous voulez. Ceci est encore plus utile si vous souhaitez auto name = get<0>(std::move(some_tuple)); le tuple avec auto name = get<0>(std::move(some_tuple)); .

OK, alors peut-être pourrions-nous étendre un peu ces syntaxes pour tenir compte de cette distinction. Peut-être &name := value; ou &name = value; signifierait déduire une référence comme auto& .

OK bien. Et ça:

 decltype(auto) name = some_thing(); 

Oh c’est vrai; C ++ a en fait deux espaces réservés: auto et decltype(auto) . L’idée de base de cette déduction est que cela fonctionne exactement comme si vous aviez fait decltype(expr) name = expr; . Donc dans notre cas, si some_thing() est un object, cela en déduira un object. Si some_thing() est une référence, cela en déduira une référence.

Ceci est très utile lorsque vous travaillez dans du code de modèle et que vous ne savez pas exactement quelle sera la valeur de retour d’une fonction. C’est idéal pour la transmission et c’est un outil essentiel, même s’il n’est pas largement utilisé.

Nous devons maintenant append plus à notre syntaxe. name ::= value; signifie “faire ce que fait decltype(auto) “. Je n’ai pas d’équivalent pour la variante Pythonique.

En regardant cette syntaxe, n’est-ce pas assez facile à sortinger par erreur? Non seulement cela, c’est à peine auto-documenté. Même si vous n’avez jamais vu decltype(auto) auparavant, il est assez évident que vous pouvez au moins facilement dire qu’il se passe quelque chose de spécial. Alors que la différence visuelle entre ::= et := est minimale.

Mais ce sont des trucs d’opinion. il y a plus de questions de fond. Voir, tout cela est basé sur l’utilisation de la syntaxe d’affectation. Eh bien … et les endroits où vous ne pouvez pas utiliser la syntaxe d’affectation? Comme ça:

 for(auto &x : container) 

Est-ce que nous changeons cela for(&x := container) ? Parce que cela semble dire quelque chose de très différent de celui de la gamme. Il semble que ce soit l’instruction d’initialisation d’une boucle régulière, et non d’une plage. Ce serait également une syntaxe différente des cas non déduits.

De plus, l’initialisation de la copie (en utilisant = ) n’est pas la même chose en C ++ que l’initialisation directe (en utilisant la syntaxe du constructeur). Alors name := value; peut ne pas fonctionner dans les cas où auto name(value) aurait.

Bien sûr, vous pourriez déclarer que := utilisera l’initialisation directe, mais cela serait assez conforme à la manière dont le rest du C ++ se comporte.

Il y a encore une chose: C ++ 14. Il nous a donné une caractéristique de déduction utile: la déduction de type de retour. Mais ceci est basé sur des espaces réservés. Tout comme pour la gamme, il est fondamentalement basé sur un typename qui est rempli par le compilateur, et non par une syntaxe appliquée à un nom et une expression particuliers.

Vous voyez, tous ces problèmes proviennent de la même source: vous inventez une syntaxe entièrement nouvelle pour déclarer des variables. Les déclarations basées sur les espaces réservés ne devaient pas inventer de nouvelle syntaxe . Ils utilisent exactement la même syntaxe qu’auparavant; ils emploient simplement un nouveau mot-clé qui agit comme un type, mais qui a une signification particulière. C’est ce qui lui permet de fonctionner en déduction basée sur la plage et pour le type de retour. C’est ce qui lui permet d’avoir plusieurs formes ( auto vs decltype(auto) ). Et ainsi de suite.

Les espaces réservés fonctionnent car ils constituent la solution la plus simple au problème, tout en conservant simultanément tous les avantages et la généralité de l’utilisation d’un nom de type réel. Si vous proposez une autre solution qui fonctionne aussi universellement que les espaces réservés, il est très peu probable que ce soit aussi simple que des espaces réservés.

À moins que ce ne soit que des orthographes avec des mots-clés ou des symboles différents …

En résumé: l’ auto pourrait être abandonnée dans certains cas, mais cela entraînerait des incohérences.

Tout d’abord, comme indiqué, la syntaxe de déclaration en C ++ est . Les déclarations explicites nécessitent un type ou au moins un mot-clé de déclaration à sa place. Nous pourrions donc utiliser var ou declare ou quelque chose, mais auto est un mot-clé de longue date en C ++, et est un bon candidat pour le mot-clé de déduction automatique de type.

Est-il possible de déclarer implicitement des variables par affectation sans tout casser?

Parfois oui. Vous ne pouvez pas effectuer d’affectation en dehors des fonctions, vous pouvez donc utiliser la syntaxe d’affectation pour les déclarations. Mais une telle approche entraînerait des incohérences dans la langue, pouvant éventuellement conduire à des erreurs humaines.

 a = 0; // Error. Could be parsed as auto declaration instead. int main() { return 0; } 

Et quand il s’agit de variables locales, les déclarations explicites sont un moyen de contrôler la scope d’une variable.

 a = 1; // use a variable declared before or outside auto b = 2; // declare a variable here 

Si une syntaxe ambiguë était autorisée, la déclaration de variables globales pourrait soudainement convertir les déclarations implicites locales en affectations. Trouver ces conversions nécessiterait de tout vérifier. Et pour éviter les collisions, vous auriez besoin de noms uniques pour tous les globals, ce qui détruit l’idée même de la scope. Donc c’est vraiment mauvais.

auto est un mot clé que vous pouvez utiliser dans des endroits où vous devez normalement spécifier un type .

  int x = some_function(); 

Peut être rendu plus générique en faisant déduire automatiquement le type int :

  auto x = some_function(); 

C’est donc une extension conservasortingce de la langue. il s’intègre dans la syntaxe existante. Sans elle x = some_function() devient une instruction d’affectation, plus une déclaration.

la syntaxe doit être non ambiguë et également compatible avec les versions antérieures.

Si auto est supprimé, il n’y aura aucun moyen de faire la distinction entre les instructions et les définitions.

 auto n = 0; // fine n=0; // statememt, n is undefined. 

Ajouter aux réponses précédentes, une note supplémentaire d’un ancien pet: Il semble que vous puissiez voir cela comme un avantage de pouvoir commencer à utiliser une nouvelle variable sans la déclarer d’aucune manière.

Dans les langues avec possibilité de définition implicite des variables, cela peut être un gros problème, en particulier dans les grands systèmes. Vous faites une faute de frappe et vous déboguez pendant des heures seulement pour découvrir que vous avez introduit involontairement une variable avec une valeur de zéro (ou pire) – blue vs bleu , label vs lable … le résultat est que vous ne pouvez vraiment faire confiance à aucun code sans vérification approfondie des noms de variables précis.

Utiliser simplement auto indique à la fois au compilateur et au responsable que vous avez l’intention de déclarer une nouvelle variable.

Pensez-y, pour pouvoir éviter ce genre de cauchemars, la déclaration «implicite none» a été introduite dans FORTRAN – et vous la voyez utilisée dans tous les programmes FORTRAN sérieux de nos jours. Ne pas l’avoir, c’est simplement … effrayant.