Pourquoi une variable const n’a-t-elle parfois pas besoin d’être capturée dans un lambda?

Prenons l’exemple suivant:

#include  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 (trunk) échouent avec une erreur dans les deux cas (la variable 'm' cannot be implicitly captured in a lambda with no capture-default specified ).

Pour une lambda à scope de bloc, les variables répondant à certains critères de la scope 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, l’ atteinte de la scope inclut toute variable locale à la fonction contenant le lambda, qui serait à la scope du lambda. Cela inclut donc m et n dans les exemples ci-dessus.

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

  • A l’intérieur du lambda, la variable ne doit pas être utilisée , ce qui signifie qu’elle ne doit subir aucune opération sauf pour:
    • apparaissant comme une expression à valeur rejetée ( m; est l’un d’entre eux), ou
    • ayant sa valeur récupérée.
  • La variable doit soit être:
    • Un entier, un entier non volatile ou un enum dont l’initialiseur était une expression constante , ou
    • Une constexpr , non volatile (ou un sous-object d’une telle)

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, suggérée par 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 (puisque de telles choses peuvent être converties en un pointeur). Pour fonctionner); il peut le faire en se référant à la variable si elle sait que la variable aura toujours la même valeur ou peut répéter la procédure pour obtenir la valeur de la variable indépendamment du contexte. Mais cela ne peut pas se faire 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 été initialisé par une expression non constante. Par conséquent, n ne peut pas être utilisé dans un lambda sans être capturé.

m été initialisé par une expression constante 42 , donc il répond aux “certains critères”. Une expression à valeur ignorée n’utilise pas l’expression, donc m; peut être utilisé sans être capturé. gcc est correct


Je dirais que la différence entre les deux compilateurs est que clang considère m; à odr-use m , mais gcc pas. 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 ex sauf si l’application de la conversion lvalue-à-rvalue à x produit une expression constante qui n’invoque aucune fonction non sortingviale et, si x est un object , ex est un élément de l’ensemble des résultats potentiels d’une expression e , où soit la conversion lvalue-à-valeur est appliquée à e , soit e est une expression à valeur rejetée.

mais en lisant attentivement, il mentionne spécifiquement qu’une expression à valeur rejetée n’utilise pas l’expression.

La version de C ++ 11 de [basic.def.odr] à l’origine n’incluait pas le cas de l’expression à valeur ignorée, donc le comportement de clang serait correct sous le C ++ 11 publié. Toutefois, le texte qui apparaît dans C ++ 14 a été accepté en tant que défaut par rapport à C ++ 11 ( problème 712 ). Les compilateurs doivent donc mettre à jour leur comportement même en mode C ++ 11.

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

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

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

Voici une citation du standard [ basic.def.odr ]:

Une variable x dont le nom apparaît comme une expression potentiellement évaluée ex est odr-used à moins que l’application de la conversion lvalue-à-rvalue à x ne donne une expression constante (…) ou e une expression à valeur rejetée.

(Supprimé partie pas si importante pour le garder court)

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

Ceci est un commentaire de MM:

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

Voir ici pour une démo .

EDIT: La version précédente de ma réponse était fausse. Le débutant est correct, voici un devis standard pertinent:

[basic.def.odr]

  1. Une variable x dont le nom apparaît comme une expression potentiellement évaluée ex est utilisée par ex sauf si l’application de la conversion lvalue-à-rvalue à x produit une expression constante qui n’invoque aucune fonction non sortingviale et, si x est un object , ex est un élément de l’ensemble des résultats potentiels d’une expression e, où soit la conversion lvalue-à-valeur est appliquée à e, soit e est une expression à valeur rejetée. …

Puisque m est une expression constante, il n’est pas utilisé et n’a donc pas besoin d’être capturé.

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