web-dev-qa-db-fra.com

Pourquoi n'est-il pas parfois nécessaire de capturer une variable const dans un lambda?

Prenons l'exemple suivant:

#include <cstdlib>

int main() {
    const int m = 42;
    [] { m; }(); // OK

    const int n = std::Rand();
    [] { n; }(); // error: 'n' is not captured
}

Pourquoi dois-je capturer n dans le deuxième lambda mais pas m dans le premier lambda? J'ai vérifié la section 5.1.2 (expressions Lambda) dans la norme C++ 14 mais je n'ai pas pu trouver de raison. Pouvez-vous m'indiquer un paragraphe dans lequel cela est expliqué?

Mise à jour: j'ai observé ce comportement avec GCC 6.3.1 et 7 (trunk). Clang 4.0 et 5 (tronc) échoue avec une erreur dans les deux cas (variable 'm' cannot be implicitly captured in a lambda with no capture-default specified).

70
s3rvac

Pour un lambda à portée de bloc, des variables répondant à certains critères dans atteignant la portée peuvent être utilisées de manière limitée à l'intérieur du lambda, même si elles ne sont pas capturées.

En gros, atteindre la portée inclut toute variable locale à la fonction contenant le lambda, qui serait dans la portée au point où le lambda a été défini. Cela inclut donc m et n dans les exemples ci-dessus.

Les "certains critères" et les "voies limitées" sont spécifiquement (à partir de C++ 14):

  • A l'intérieur du lambda, la variable ne doit pas être utilisée par odr , ce qui signifie qu'elle ne doit subir aucune opération à l'exception de:
    • apparaissant comme une expression de valeur supprimée (m; en fait partie), ou
    • ayant sa valeur récupérée.
  • La variable doit être soit:
    • Un const, non -volatile entier ou énum dont l'initialiseur était une expression constante , ou
    • Une variable constexpr, non_volatile (ou un sous-objet de cette variable)

Références à C++ 14: [expr.const] /2.7, [basic.def.odr]/3 (première phrase), [expr.prim.lambda]/12, [expr.prim.lambda]/10.

La justification de ces règles, comme le suggèrent d'autres commentaires/réponses, est que le compilateur doit être capable de "synthétiser" un lambda sans capture en tant que fonction libre indépendante du bloc (car de telles choses peuvent être converties en un pointeur). Pour fonctionner); il peut le faire en se référant à la variable s'il sait que la variable aurait toujours la même valeur, ou il peut répéter la procédure pour obtenir la valeur de la variable indépendamment du contexte. Mais il ne peut pas faire cela si la variable peut différer de temps en temps, ou si l'adresse de la variable est nécessaire par exemple.


Dans votre code, n a été initialisé par une expression non constante. Par conséquent, n ne peut pas être utilisé dans un lambda sans être capturé.

m a été initialisé par une expression constante 42, il répond donc à "certains critères". Une expression à valeur ignorée ne l'utilise pas odr, donc m; peut être utilisé sans que m soit capturé. gcc est correct.


Je dirais que la différence entre les deux compilateurs est que clang considère m; à odr-use m, mais pas gcc. La première phrase de [basic.def.odr]/3 est assez compliquée:

Une variable x dont le nom apparaît comme une expression potentiellement évaluée ex est utilisée par odr par ex sauf si l'application de la conversion lvalue-to-rvalue à x produit une expression constante qui n'invoque aucune fonction non triviale et, si x est un objet, ex est un élément de l'ensemble des résultats potentiels d'une expression e, où la conversion lvalue-to-rvalue est appliquée à e, ou e est une valeur ignorée expression.

mais en lisant attentivement, il mentionne spécifiquement qu'une expression de valeur rejetée n'utilise pas odr-use l'expression.

La version de C++ 11 de [basic.def.odr] ne comprenait pas à l'origine le cas d'expression de valeur rejetée, donc le comportement de clang serait correct sous le C++ 11 publié. Cependant, le texte qui apparaît en C++ 14 a été accepté comme un défaut par rapport à C++ 11 ( problème 712 ), donc les compilateurs doivent mettre à jour leur comportement même en mode C++ 11.

50
M.M

C'est parce que c'est une expression constante, le compilateur traite comme si c'était [] { 42; }();

La règle dans [ expr.prim.lambda ] est:

Si une expression lambda ou une instanciation du modèle d'opérateur d'appel de fonction d'un odr lambda générique utilise (3.2) ceci ou une variable avec une durée de stockage automatique à partir de sa portée, cette entité doit être capturée par l'expression lambda.

Voici une citation de la norme [ basic.def.odr ]:

Une variable x dont le nom apparaît comme une expression potentiellement évaluée ex est utilisée odr sauf si l'application de la conversion lvalue-to-rvalue à x donne une expression constante (...) ou e est une expression à valeur rejetée.

(Suppression de la partie pas si importante pour rester courte)

Ma compréhension simple est: le compilateur sait que m est constant au moment de la compilation, tandis que n changera au moment de l'exécution et donc n doit être capturé. n serait utilisé par odr, car vous devez réellement regarder ce qu'il y a à l'intérieur de n au moment de l'exécution. En d'autres termes, le fait qu'il ne peut y avoir qu'une seule définition de n est pertinent.

Voici un commentaire de M.M:

m est une expression constante car c'est une variable automatique const avec initialiseur d'expression constante, mais n n'est pas une expression constante car son initialiseur n'était pas une expression constante. Ceci est couvert dans [expr.const] /2.7. L'expression constante n'est pas utilisée par ODR, selon la première phrase de [basic.def.odr]/3

Voir ici pour une démo .

33
Beginner

EDIT: La version précédente de ma réponse était erronée. Le débutant est correct, voici une citation standard pertinente:

[basic.def.odr]

  1. Une variable x dont le nom apparaît comme une expression potentiellement évaluée ex est utilisée par odr par ex à moins d'appliquer lvalue-à-rvalue la conversion en x donne un expression constante qui n'invoque aucune fonction non triviale et, si x est un objet, ex est un élément de l'ensemble des résultats potentiels d'une expression e, où soit la conversion de lvalue en rvalue est appliquée à e, ou e est une expression de valeur rejetée. ...

Étant donné que m est une expression constante, elle n'est pas utilisée par odr et n'a donc pas besoin d'être capturée.

Il semble que le comportement des clangs ne soit pas conforme à la norme.

2
eerorika