Tratando de entender las lambdas

3 minutos de lectura

Tratando de entender lambdas en C++, lo que no entiendo es esto:

int multiplier = 5;
auto timesFive = [multiplier](int a) { return a * multiplier; }; 
std::cout << timesFive(2) << '\n'; // Prints 10

multiplier = 15;
std::cout << timesFive(2) << '\n'; // Still prints 2*5 == 10 (???) - Should it be 30?

Cuando el programa llama al timesFive() la segunda vez, espero que el resultado sea 30. Pero, ¿por qué el resultado es Still prints 2*5 == 10no prints 2*15 == 30? Quizás la función lambda de alguna manera no puede rastrear el valor de multipliera pesar de que ya hemos intentado capturarlo?

¿Y cuál es la manera de obtener el resultado deseado?

  • Por favor, haga que el título describa el problema. Sí, está tratando de entender las lambdas, pero eso no nos dice mucho sobre lo que realmente está preguntando. (Estaba considerando editarlo yo mismo, pero no me gustaron las ideas que se me ocurrieron).

    – jpmc26

    29 ago 2016 a las 21:02


avatar de usuario
Rudi

capturaste multiplier por valor, lo que significa que se copió en la lambda. Necesitas capturarlo por referencia:

int multiplier = 5;
auto timesFive = [&multiplier](int a) { return a * multiplier; }; 
std::cout << timesFive(2);

multiplier = 15;
std::cout << timesFive(2); 

  • Además, si realmente desea este comportamiento, el nombre timesFive es más que un poco engañoso

    –Steve Cox

    29 de agosto de 2016 a las 14:03

  • @SteveCox Estoy bastante seguro de que este es solo un ejemplo académico que muestra un concepto. También podríamos llamarlo foo, foobar, …. Por supuesto que tienes razón, si ese fuera un ejemplo del mundo real.

    – exiliar

    2 de septiembre de 2016 a las 7:02

Lambdas son azúcar sintáctico para una clase innombrable y la instancia de la misma. A veces, expandir su código a lo que esta clase innombrable puede ayudar a comprender lo que está sucediendo.

[ capture_list ]( arg_list ) -> return_value_clause_opt { body };

se vuelve muy aproximadamente (pseudo-código):

struct anonymous_type {
  capture_list;
  auto operator()( arg_list ) const -> return_value_clause_opt {
    body
  }
  anonymous_type( capture_list_in ):capture_list(capture_list_in) {}
};

Si lista una variable en capture_list por su simple nombre, es copiado en una copia dentro de la clase anónima.

Entonces tus timesFive convertirse

struct __secret_name__ {
  int multiplier;
  int operator()(int a) const { return a*multiplier; }
};
int multiplier = 5;
auto timesFive = __secret_name__{multiplier};

Debería quedar bastante claro que cambiar multiplier en el código anterior no cambiará el comportamiento de timesFive.

si pones un & delante del nombre, un no-const referencia se coloca dentro de la clase anónima.

struct __secret_name__ {
  int& multiplier;
  int operator()(int a) const { return a*multiplier; }
};
int multiplier = 5;
auto timesFive = __secret_name__{multiplier};

ahora, cambiando multiplier va a cambiar el comportamiento de timesFiveporque timesFive sostiene un referencia al multiplicador, no una copia del mismo.


Algunos detalles saltados arriba por brevedad. El nombre __secret_name__ es solo para exposición. Las variables miembro de la lamba no son realmente públicas. La lambda, que es trivialmente construible, está definida por la implementación, incluso si sus datos lo están. Etc.

  • Buena idea. Hace mucho tiempo, teníamos Cfront, cortesía de Bjarne Stroustrup, para obtener el “funcionamiento interno” de C++. ¿Hay algo similar en estos días? ¿Cómo conjeturaste tu pseudocódigo? ¿intuición?

    – lápiz negro

    30 de agosto de 2016 a las 0:31

  • @black el estándar describe qué es una lambda. No es tan “como si” como for(:) bucles, pero es muy explícito.

    – Yakk – Adam Nevraumont

    7 sep 2016 a las 12:34

¿Ha sido útil esta solución?