(Opcional) C++ — expresiones lambda
Introducción
| Fuente | Recurso | Notas |
|---|---|---|
| CPP | Lambda expressions | referencia |
| UMich | Using C++ Lambdas | |
| SO | What is a lambda expression in C++11? | |
| Microsoft | Lambda Expressions in C++ |
| Fuente | Recurso | Notas |
|---|---|---|
| SO | Type of a lambda function using auto? | |
| SO | What is meant by 'retain state' in c++? |
¿Por qué usar lambdas?
Las lambdas permiten escribir funciones simples y anónimas en línea. Así se pueden escribir funciones más chicas manteniendo el código local y organizado. Además, las lambdas pueden capturar las variables del entorno, lo que suele ser conveniente al escribir funciones auxiliares.
La biblioteca estándar de C++ tiene buen soporte para lambdas, con funciones
como std::sort y std::lower_bound que permiten pasar funciones
personalizadas para actuar como comparador de objetos. Suele ser más
conveniente que la alternativa de escribir una función dedicada como
comparador.
Forma general
Las lambdas tienen la siguiente forma:
[capture_list](parameters) -> trailing_return_type {
// function body
}Los parámetros, el tipo de retorno y el cuerpo de la función son bastante directos. En programación competitiva, en general se usan estos tipos de captura:
[]: No captura nada del alcance local.
[&]: Captura todo el alcance local por referencia.
[=]: Captura todo el alcance local por copia.
El alcance local de la lambda es el alcance donde se define, no el alcance donde se usa.
También se puede especificar qué variables capturar, pero normalmente no es necesario.
Como ejemplo de lambda, supongamos que se quiere escribir una función que devuelva el cuadrado de un número dado. Se puede escribir la siguiente expresión lambda:
auto square = [](int x) -> long long { return (long long)x * x; };Después se puede llamar a la lambda como una función normal.
cout << square(10) << '\n'; // prints out 100Como se menciona en el módulo de comparadores personalizados, las lambdas también se pueden usar como comparadores.
Lambdas recursivas
Supongamos que se quiere escribir una función recursiva de GCD. Con una lambda, la forma más directa de escribirla sería así:
auto gcd = [](int a, int b) -> int { return b == 0 ? a : gcd(b, a % b); };Por desgracia, esto no funciona, porque una lambda no puede referenciarse directamente a sí misma en su definición. Hay, no obstante, formas de evitarlo.
Con y_combinator
| Fuente | Recurso | Notas |
|---|---|---|
| open-std | Y Combinator Proposal | |
| RIP Tutorial | Recursive Lambdas |
Si se agrega lo siguiente del enlace de arriba en C++14:
namespace std {
template <class Fun> class y_combinator_result {
Fun fun_;
public:
template <class T>
explicit y_combinator_result(T &&fun) : fun_(std::forward<T>(fun)) {}
template <class... Args> decltype(auto) operator()(Args &&...args) {
return fun_(std::ref(*this), std::forward<Args>(args)...);
}
};
template <class Fun> decltype(auto) y_combinator(Fun &&fun) {
return y_combinator_result<std::decay_t<Fun>>(std::forward<Fun>(fun));
}
} // namespace stdEntonces se puede escribir código como el siguiente:
int main() {
cout << y_combinator([](auto gcd, int a, int b) -> int {
return b == 0 ? a : gcd(b, a % b);
})(20, 30)
<< "\n"; // outputs 10
}Con std::function
En lugar de auto, usar function<return_type(param)>.
int main() {
function<int(int, int)> gcd = [&](int a, int b) {
return b == 0 ? a : gcd(b, a % b);
};
cout << gcd(20, 30) << '\n'; // outputs 10
}Con lambdas genéricas
Para resolver el problema de que la lambda no puede acceder a sí misma, se pasa a sí misma.
int main() {
auto gcd = [&](int a, int b, auto &&gcd) -> int {
return b == 0 ? a : gcd(b, a % b, gcd);
};
cout << gcd(20, 30, gcd) << '\n'; // outputs 10
}