Capture Lambda et paramètre du même nom - qui fait de l'ombre à l'autre? (clang vs gcc)


125
auto foo = "You're using g++!";
auto compiler_detector = [foo](auto foo) { std::puts(foo); };
compiler_detector("You're using clang++!");
  • clang ++ 3.6.0 et plus récent imprimer "Vous utilisez clang ++!" et avertir que la capture foo n'est pas utilisée.

  • g ++ 4.9.0 et plus récent imprimer "Vous utilisez g ++!" et avertir que le paramètre foo n'est pas utilisé.

Quel compilateur suit plus précisément la norme C ++ ici?

exemple de boîte à baguette


1
Coller le code de wandbox ici (ils semblent avoir oublié le bouton de partage) donne l'impression que VS2015 (?) Est d'accord avec l' avertissement C4458: la déclaration de «foo» cache un membre de la classe .
nwp

12
Great example ..
deviantfan

4
Le lambda a un type avec un opérateur d'appel de fonction de modèle, donc la logique me ferait dire que le paramètre devrait masquer la variable capturée comme si dans struct Lambda { template<typename T> void operator()(T foo) const { /* ... */ } private: decltype(outer_foo) foo{outer_foo}; }.
skypjack

2
@nwp VS est erroné, les membres de données du lambda ne sont pas nommés et ne peuvent donc pas être masqués. La norme dit que "l'accès à une entité capturée est transformé pour accéder au membre de données correspondant", ce qui nous laisse à la case départ.
n. «pronoms» m.

10
J'espère que la version clang est correcte - ce serait une innovation si quelque chose en dehors d'une fonction occultait le paramètre de fonction, au lieu de l'inverse!
MM

Réponses:


65

Mise à jour: comme promis par le président Core dans la citation du bas, le code est maintenant mal formé :

Si un identifiant dans une simple capture apparaît comme le déclarateur-id d'un paramètre du lambda-declarator du paramètre-déclaration article , le programme est mal formé.


Il y a quelques temps, il y a eu quelques problèmes concernant la recherche de nom dans lambdas. Ils ont été résolus par N2927 :

Le nouveau libellé ne repose plus sur la recherche pour remapper les utilisations des entités capturées. Il nie plus clairement les interprétations selon lesquelles une instruction composée de lambda est traitée en deux passes ou que tous les noms de cette instruction composée pourraient être résolus en un membre du type de fermeture.

La recherche est toujours effectuée dans le contexte de l' expression lambda , jamais «après» la transformation en corps de fonction membre d'un type de fermeture. Voir [expr.prim.lambda] / 8 :

Le lambda-expression « de instruction_compound donne la fonction du corps ([dcl.fct.def]) de l'opérateur d'appel de fonction, mais à des fins de recherche par nom, [...], le instruction_compound est considéré dans le contexte de l' expression lambda . [ Exemple :

struct S1 {
  int x, y;
  int operator()(int);
  void f() {
    [=]()->int {
      return operator()(this->x+y);  // equivalent to: S1::operator()(this->x+(*this).y)
                                     // and this has type S1*
    }; 
  }
};

- fin d'exemple ]

(L'exemple montre également clairement que la recherche ne considère pas en quelque sorte le membre de capture généré du type de fermeture.)

Le nom foon'est pas (re) déclaré dans la capture; il est déclaré dans le bloc contenant l'expression lambda. Le paramètre fooest déclaré dans un bloc imbriqué dans ce bloc externe (voir [basic.scope.block] / 2 , qui mentionne également explicitement les paramètres lambda). L'ordre de recherche va clairement des blocs internes aux blocs externes . Par conséquent, le paramètre doit être sélectionné, c'est-à-dire que Clang a raison.

Si vous deviez faire de la capture un init-capture, c'est-à-dire foo = ""au lieu de foo, la réponse ne serait pas claire. En effet, la capture induit désormais une déclaration dont le "bloc" n'est pas donné. J'ai envoyé un message à la chaise principale à ce sujet, qui a répondu

Il s'agit du numéro 2211 (une nouvelle liste de problèmes apparaîtra sous peu sur le site open-std.org, malheureusement avec juste des espaces réservés pour un certain nombre de problèmes, dont celui-ci; je travaille dur pour combler ces lacunes avant le Kona réunion à la fin du mois). Le CWG en a discuté lors de notre téléconférence de janvier, et la direction est de rendre le programme mal formé si un nom de capture est également un nom de paramètre.


Rien pour moi de déchirer ici :) Une simple capture ne déclare rien, donc le résultat correct de la recherche de nom est assez évident (BTW, GCC le fait bien si vous utilisez une capture par défaut au lieu d'une capture explicite). Les init-capture sont un peu plus délicates.
TC

1
@TC Je suis d'accord. J'ai déposé un problème de base, mais apparemment, cela a déjà été discuté, voir la réponse modifiée.
Columbo

6

J'essaie de rassembler quelques commentaires sur la question pour vous donner une réponse significative.
Tout d'abord, notez que:

  • Les membres de données non statiques sont déclarés pour le lambda pour chaque variable capturée par copie
  • Dans le cas spécifique, le lambda a un type de fermeture qui a un opérateur d'appel de fonction de modèle en ligne public acceptant un paramètre nommé foo

Par conséquent, la logique me ferait dire au premier coup d'œil que le paramètre devrait masquer la variable capturée comme dans:

struct Lambda {
    template<typename T> void operator()(T foo) const { /* ... */ }
    private: decltype(outer_foo) foo{outer_foo};
};

Quoi qu'il en soit, @nm a correctement noté que les membres de données non statiques déclarés pour les variables capturées par copie ne sont en fait pas nommés. Cela étant dit, le membre de données sans nom est toujours accessible au moyen d'un identificateur (c'est-à-dire foo). Par conséquent, le nom du paramètre de l'opérateur d'appel de fonction doit toujours (laissez-moi dire) masquer cet identificateur .
Comme l'a correctement souligné @nm dans les commentaires de la question:

l'entité capturée d'origine [...] doit être masquée normalement conformément aux règles de portée

À cause de cela, je dirais que le bruit est juste.


Comme expliqué ci-dessus, la recherche dans ce contexte n'est jamais effectuée comme si nous étions dans le type de fermeture transformé.
Columbo

@Columbo J'ajoute une ligne que j'ai manquée même si cela ressortait clairement du raisonnement, c'est que le cliquetis a raison. Le plus drôle est que j'ai trouvé [expr.prim.lambda] / 8 en essayant de donner une réponse, mais je n'ai pas pu l'utiliser correctement comme vous l'avez fait. C'est pourquoi à chaque fois c'est un plaisir de lire vos réponses. ;-)
skypjack
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.