¿Por qué a veces no es necesario capturar una variable const en una lambda?

76

Considere el siguiente ejemplo:

#include <cstdlib>

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

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

¿Por qué necesito capturar nen la segunda lambda pero no men la primera lambda? Revisé la sección 5.1.2 ( Expresiones Lambda ) en el estándar C ++ 14 pero no pude encontrar una razón. ¿Puede señalarme un párrafo en el que se explique esto?

Actualización: observé este comportamiento con GCC 6.3.1 y 7 (troncal). Clang 4.0 y 5 (troncal) fallan con un error en ambos casos ( variable 'm' cannot be implicitly captured in a lambda with no capture-default specified).

s3rvac
fuente
2
constexprvsconst
CinCout
1
clamg acepta el primero si cambia m;am + 0;
MM
4
La misma razón por la que std::array<int,m>está bien y std::array<int,n>no.
n. 'pronombres' m.
3
Ojalá las variables no lo fueran a constexprmenos que lo especifiques explícitamente. Si alguien quiere una constexprvariable, debe declararla como una.
xinaiz
2
@BlackMoses: mno lo es constexpr, es una expresión integral constante en tiempo de compilación ... que era algo mucho antes de que constexprse inventara la palabra clave.
Ben Voigt

Respuestas:

56

Para una lambda en el alcance del bloque, las variables que cumplen con ciertos criterios en el alcance de alcance pueden usarse de manera limitada dentro de la lambda, incluso si no se capturan.

En términos generales, alcanzar el alcance incluye cualquier variable local a la función que contiene el lambda, que estaría dentro del alcance en el punto en que se definió el lambda. Entonces esto incluye my nen los ejemplos anteriores.

Los "ciertos criterios" y las "formas limitadas" son específicamente (a partir de C ++ 14):

  • Dentro de la lambda, la variable no debe ser usada odr , lo que significa que no debe sufrir ninguna operación excepto por:
    • que aparece como una expresión de valor descartado ( m;es uno de estos), o
    • recuperar su valor.
  • La variable debe ser:
    • A const, no volatileentero o enumeración cuyo inicializador era una expresión constante , o
    • A constexpr, no volatilevariable (o un subobjeto de los mismos)

Referencias a C ++ 14: [expr.const] /2.7, [basic.def.odr] / 3 (primera oración), [expr.prim.lambda] / 12, [expr.prim.lambda] / 10.

El fundamento de estas reglas, como lo sugieren otros comentarios / respuestas, es que el compilador necesita poder "sintetizar" una lambda sin captura como una función libre independiente del bloque (ya que tales cosas se pueden convertir en un puntero) funcionar); puede hacerlo a pesar de referirse a la variable si sabe que la variable siempre tendrá el mismo valor, o puede repetir el procedimiento para obtener el valor de la variable independientemente del contexto. Pero no puede hacer esto si la variable puede diferir de vez en cuando, o si se necesita la dirección de la variable, por ejemplo.


En su código, nfue inicializado por una expresión no constante. Por nlo tanto , no se puede utilizar en una lambda sin ser capturado.

mse inicializó mediante una expresión constante 42, por lo que cumple con "ciertos criterios". Una expresión de valor descartado no odr-usa la expresión, por lo que m;puede usarse sin mser capturada. gcc es correcto.


Yo diría que la diferencia entre los dos compiladores es que clang considera m;odr-use m, pero gcc no. La primera oración de [basic.def.odr] / 3 es bastante complicada:

Una variable xcuyo nombre aparece como una expresión potencialmente evaluada exes utilizada por odr aex menos que la aplicación de la conversión lvalue-a-rvalue xproduzca una expresión constante que no invoca ninguna función no trivial y, si xes un objeto, exes un elemento de el conjunto de resultados potenciales de una expresión e, donde se aplica la conversión de lvalue a rvalue eo ees una expresión de valor descartado.

pero al leer con atención, menciona específicamente que una expresión de valor descartado no utiliza odr la expresión.

La versión de C ++ 11 de [basic.def.odr] originalmente no incluía el caso de expresión de valor descartado, por lo que el comportamiento de clang sería correcto bajo el C ++ 11 publicado. Sin embargo, el texto que aparece en C ++ 14 se aceptó como un Defecto contra C ++ 11 ( Problema 712 ), por lo que los compiladores deberían actualizar su comportamiento incluso en el modo C ++ 11.

MM
fuente
Creo que [expr] / 11 significa que la conversión de lvalue a rvalue no se aplica en este caso.
cpplearner
@cpplearner No vi eso antes, pero estoy de acuerdo con usted ahora que lo señaló, gracias ... actualizaré mi respuesta
MM
Leyendo de nuevo, parece que la m;conversión de lvalor a rvalor es irrelevante. La definición de odr-use en [basic.def.odr] / 3 dice "o se aplica la conversión lvalue-a-rvalue eo ees una expresión de valor descartado", y aquí la expresión mes una expresión de valor descartado, así que al final la variable mno se usa odr.
cpplearner
@cpplearner Roger, leí mal "expresión de valor descartado" como "expresión no evaluada"
MM
34

Es porque es una expresión constante, el compilador la trata como si fuera [] { 42; }();

La regla en [ expr.prim.lambda ] es:

Si una expresión lambda o una instanciación de la plantilla de operador de llamada de función de una lambda odr-genérica usa (3.2) esta o una variable con duración de almacenamiento automático desde su alcance, esa entidad será capturada por la expresión lambda.

Aquí una cita del estándar [ basic.def.odr ]:

Una variable x cuyo nombre aparece como una expresión potencialmente evaluada ex se usa odr a menos que la aplicación de la conversión de lvalor a rvalue x produzca una expresión constante (...) o e sea una expresión de valor descartado.

(Se eliminó la parte no tan importante para que sea breve)

Mi comprensión simple es: el compilador sabe que mes constante en tiempo de compilación, mientras nque cambiará en tiempo de ejecución y, por lo tanto, ndebe capturarse. nse usaría odr, porque en realidad debe echar un vistazo a lo que hay dentro nen tiempo de ejecución. En otras palabras, el hecho de que "sólo puede haber una" definición de nes relevante.

Esto es de un comentario de MM:

m es una expresión constante porque es una variable automática constante con inicializador de expresión constante, pero n no es una expresión constante porque su inicializador no era una expresión constante. Esto se trata en [expr.const] /2.7. La expresión constante no se utiliza ODR, según la primera oración de [basic.def.odr] / 3

Vea aquí una demostración .

Principiante
fuente
5
Sería bueno explicar con un poco más de detalle por qué n; odr-uses n , pero m;no odr-use m
MM
1
No veo cómo ese párrafo es relevante, ya que dice que la entidad será capturada por la expresión lambda. En la primera lambda, no hay captura y, sin embargo, GCC la compila.
s3rvac
@ s3rvac Ver comentario de MM La clave aquí es que en un caso hay uso de odr, pero no en el otro.
Ilya Popov
@IlyaPopov Las variables se usan de la misma manera en ambas lambdas, por lo que no veo por qué debería haber uso de odr en una lambda y no en la otra lambda.
s3rvac
2
@ s3rvac el párrafo dice " Si [se cumplen las condiciones], esa entidad será capturada". Se cumplen las condiciones para npero no m.
MM
2

EDITAR: La versión anterior de mi respuesta era incorrecta. El principiante es correcto, aquí hay una cotización estándar relevante:

[basic.def.odr]

  1. Una variable x cuyo nombre aparece como una expresión potencialmente evaluada ex es usada por odr por ex a menos que la aplicación de la conversión de lvalor a rvalue ax produzca una expresión constante que no invoca funciones no triviales y, si x es un objeto , ex es un elemento del conjunto de resultados potenciales de una expresión e, donde la conversión de lvalor a rvalue se aplica ae, o e es una expresión de valor descartado. ...

Dado que mes una expresión constante, no se usa odr y, por lo tanto, no es necesario capturarla.

Parece que el comportamiento de los sonidos metálicos no cumple con el estándar.

eerorika
fuente
El primero realmente funciona, pruébalo aquí: ideone.com/o8WIO1
Principiante
Creo que esto está bien, vea la cita del estándar. ¿Cómo lo ves?
Principiante
@Principiante, esa cita en realidad confirma mi respuesta. "deberá" impone una restricción al programa. Significa que la variable debe ser capturada. Dado que no fue capturado (ni explícitamente ni implícitamente mediante el uso de captura predeterminada), el programa viola esa regla.
eerorika
Variables automáticas del alcance de alcance que son expresiones constantes, no necesitan ser capturadas
MM
1
@MM eso es interesante. ¿Puedes encontrar la regla que lo dice?
eerorika