Décltype C ++ et parenthèses - pourquoi?

32

Le sujet a été discuté auparavant , mais ce n'est pas un doublon.

Lorsque quelqu'un demande la différence entre decltype(a)et decltype((a)), la réponse habituelle est - aest une variable, (a)est une expression. Je trouve cette réponse insatisfaisante.

Tout d'abord, ac'est aussi une expression. Les options pour une expression primaire incluent, entre autres -

  • ( expression )
  • id-expression

Plus important encore, le phrasé pour decltype considère les parenthèses très, très explicitement :

For an expression e, the type denoted by decltype(e) is defined as follows:
(1.1)  if e is an unparenthesized id-expression naming a structured binding, ...
(1.2)  otherwise, if e is an unparenthesized id-expression naming a non-type template-parameter, ...
(1.3)  otherwise, if e is an unparenthesized id-expression or an unparenthesized class member access, ...
(1.4)  otherwise, ...

La question demeure donc. Pourquoi les parenthèses sont - elles traitées différemment? Quelqu'un connaît-il les documents techniques ou les discussions en comité qui se cachent derrière? La considération explicite des parenthèses conduit à penser que ce n'est pas un oubli, donc il doit y avoir une raison technique qui me manque.

Ofek Shilon
la source
2
"la réponse habituelle est - a est une variable, (a) est une expression" Ce qu'ils veulent dire " (a)est une expression, et aest une expression et une variable".
HolyBlackCat

Réponses:

18

Ce n'est pas un oubli. Il est intéressant de noter que dans Decltype et auto (révision 4) (N1705 = 04-0145), il y a une déclaration:

Les règles decltype indiquent maintenant explicitement cela decltype((e)) == decltype(e)(comme suggéré par EWG).

Mais dans Decltype (révision 6): libellé proposé (N2115 = 06-018) l' un des changements est

L'expression entre parenthèses dans decltype n'est pas considérée comme un id-expression.

Il n'y a aucune justification dans le libellé, mais je suppose que c'est une sorte d'extension du decltype en utilisant une syntaxe un peu différente, en d'autres termes, il était destiné à différencier ces cas.

L'utilisation pour cela est montrée dans C ++ draft9.2.8.4:

const int&& foo();
int i;
struct A { double x; };
const A* a = new A();
decltype(foo()) x1 = 17;        // type is const int&&
decltype(i) x2;                 // type is int
decltype(a->x) x3;              // type is double
decltype((a->x)) x4 = x3;       // type is const double&

Ce qui est vraiment intéressant, c'est comment cela fonctionne avec la returndéclaration:

decltype(auto) f()
{
    int i{ 0 };
    return (i);
}

Mon Visual Studio 2019 me suggère de supprimer les parenthèses redondantes, mais en fait, elles se transforment en decltype((i))changements qui retournent la valeur à int&ce qui en fait UB depuis le retour de la référence à une variable locale.

espkk
la source
Merci d'avoir identifié l'origine! Non seulement le decltype aurait pu être spécifié différemment, il s'avère qu'il l'était initialement. Le document rev-6 ajoute explicitement ce comportement amusant entre parenthèses, mais saute la justification :(. Je suppose que c'est aussi proche d'une réponse que nous en aurions maintenant ..
Ofek Shilon
Et pourtant, personne n'a proposé de résoudre le problème idiot en en faisant deux mots-clés distincts, pour deux fonctions distinctes? L'une des plus grosses bévues du comité (qui a historiquement fait beaucoup d'erreurs directes).
curiousguy
13

Pourquoi les parenthèses sont-elles traitées différemment?

Les parenthèses ne sont pas traitées différemment. C'est l'expression id sans parenthèse qui est traitée différemment.

Lorsque les parenthèses sont présentes, les règles normales pour toutes les expressions s'appliquent. Le type et la catégorie de valeur sont extraits et codifiés dans le type de decltype.

La disposition spéciale est là pour que nous puissions écrire plus facilement du code utile. Lors de l'application decltypeau nom d'une variable (membre), nous ne voulons généralement pas d'un type qui représente les propriétés de la variable lorsqu'elle est traitée comme une expression. Au lieu de cela, nous voulons juste le type avec lequel la variable est déclarée, sans avoir à appliquer une tonne de traits de type pour y accéder. Et c'est exactement ce qui decltypeest spécifié pour nous donner.

Si nous nous soucions des propriétés de la variable en tant qu'expression, nous pouvons toujours l'obtenir assez facilement, avec une paire de parenthèses supplémentaire.

Conteur - Unslander Monica
la source
Donc, pour un intmembre ide a, decltype(a.i)est inttout decltype((a.i))est int&(en supposant que anon const)? Puisque l'expression a.iest assignable?
n314159
1
@ n314159 - C'est l'essentiel. L'expression a.iest une valeur l non constante, vous obtenez donc un type de référence valeur non constante pour (a.i).
StoryTeller - Unslander Monica
Pourquoi les «règles régulières pour toutes les expressions» indiquent-elles que leur type est une référence? Pourquoi «les propriétés de la variable en tant qu'expression» incluent-elles la propriété d'être une référence? S'agit-il d'un défaut naturel?
Ofek Shilon
1
@OfekShilon - Leur type n'est pas une référence. Le type d'une expression n'est jamais analysé comme référence . Mais dectlype ne peut se résoudre qu'en type, et il est censé nous dire non seulement le type d'une expression, mais aussi sa catégorie de valeur. La catégorie de valeur est codifiée par types de référence. Les valeurs l sont &, les valeurs x le sont &&et les valeurs pr ne sont pas des types de référence.
StoryTeller - Unslander Monica
@StoryTeller exactement, il n'y a pas de différence «naturelle» entre les types d'expressions et les non-expressions. Ce qui rend la distinction artificielle.
Ofek Shilon
1

Avant C ++ 11, le langage a besoin d'outils pour obtenir deux types d'informations différents :

  • le type d'une expression
  • le type d'une variable telle qu'elle a été déclarée

En raison de la nature de ces informations, les fonctionnalités ont dû être ajoutées dans la langue (cela ne peut pas être fait dans une bibliothèque). Cela signifie de nouveaux mots-clés. La norme aurait pu introduire deux nouveaux mots clés pour cela. Par exemple exprtypepour obtenir le type d'une expression et decltypepour obtenir le type de déclaration d'une variable. Cela aurait été l'option claire et heureuse.

Cependant, le comité standard a toujours fait de son mieux pour éviter d'introduire de nouveaux mots clés dans la langue afin de minimiser la rupture de l'ancien code. La rétrocompatibilité est une philosophie fondamentale du langage.

Donc , avec C ++ 11 nous avons eu un seul mot - clé utilisé pour deux choses différentes: decltype. La façon dont il différencie les deux utilisations consiste à traiter decltype(id-expression)différemment. C'était une décision consciente du comité, un (petit) compromis.

bolov
la source
Je me souviens avoir entendu cela lors d'une conférence cpp. Je n'ai cependant aucun espoir de trouver la source. Ce serait génial si quelqu'un le trouvait.
bolov
1
C ++ a historiquement ajouté un tas de nouveaux mots clés, beaucoup sans soulignement et certains avec un mot anglais. Le rationnel est vraiment absurde.
curiousguy
@curiousguy chaque mot-clé introduit entrera en conflit avec les symboles d'utilisateur existants, de sorte que le comité accorde beaucoup d'importance à la décision d'ajouter de nouveaux mots-clés réservés à la langue. Ce n'est pas absurde imo.
bolov
Pas vrai: a exportété introduit. Si vous pouvez avoir export(auparavant tous les modèles étaient par défaut "exportés"), vous pouvez avoir des trucs comme decltypeet constexpr. De toute évidence, l'ajout registerdans une autre langue serait problématique.
curiousguy
@bolov Merci! (1) S'agit-il de spéculations ou de connaissances? Etes-vous au courant de discussions où l'évitement d'un mot clé supplémentaire était la motivation? (2) Pouvez-vous donner un exemple où une expression id doit en fait être traitée différemment si elle est utilisée "comme expression"? (Qu'est-ce que cela signifie même?)
Ofek Shilon