Programación funcional en C++: de lambdas a composición
La programación funcional (PF) es un paradigma en el que los cálculos se basan en la aplicación de funciones puras, la inmutabilidad de los datos y la ausencia de efectos secundarios. Aunque C++ fue originalmente un lenguaje multiparadigma, sus estándares modernos (C++11/14/17/20) introdujeron potentes herramientas para PF: expresiones lambda, std::function, algoritmos STL, corrutinas e incluso soporte para patrones monádicos. En este artículo, analizaremos cómo escribir código funcional en C++ sin perder rendimiento.
1. Fundamentos del estilo funcional: lambdas y std::function
Las expresiones lambda son el corazón de la PF en C++. Permiten crear funciones anónimas «sobre la marcha», capturando el contexto.
#include <iostream>#include <vector>#include <algorithm>
int main() { std::vector<int> nums = {1, 2, 3, 4, 5}; // Lambda para filtrar números pares auto is_even = [](int x) { return x % 2 == 0; }; // Uso con el algoritmo std::copy_if std::vector<int> evens; std::copy_if(nums.begin(), nums.end(), std::back_inserter(evens), is_even); for (int n : evens) std::cout << n << " "; // 2 4 return 0;}Puntos clave de las lambdas:
- Captura —
[=](por valor),[&](por referencia),[this](para métodos de clase). - Tipo — cada lambda tiene un tipo único, por lo que para pasarla a una función se usa
std::functiono plantillas. - Valor de retorno — se deduce automáticamente, a menos que se especifique lo contrario.
std::function — un envoltorio para objetos invocables (funciones, lambdas, functores). Es útil para almacenar y pasar, pero añade sobrecarga (llamada virtual).
#include <functional>
void apply_twice(std::function<int(int)> f, int x) { std::cout << f(f(x)) << std::endl; // Composición}
int main() { auto square = [](int x) { return x * x; }; apply_twice(square, 3); // (3^2)^2 = 81 return 0;}2. Algoritmos STL como base de cadenas funcionales
Los algoritmos STL (std::transform, std::copy_if, std::accumulate, etc.) encajan perfectamente con el estilo funcional. No modifican los contenedores directamente, sino que toman iteradores y devuelven resultados.
Ejemplo: filtrado y transformación (map/filter)
#include <numeric>#include <ranges> // C++20
int main() { std::vector<int> data = {1, 2, 3, 4, 5, 6}; // Enfoque clásico (C++11) std::vector<int> filtered; std::copy_if(data.begin(), data.end(), std::back_inserter(filtered), [](int x) { return x > 3; }); std::vector<int> result; std::transform(filtered.begin(), filtered.end(), std::back_inserter(result), [](int x) { return x * 2; }); // Enfoque moderno con ranges (C++20) auto view = data | std::views::filter([](int x) { return x > 3; }) | std::views::transform([](int x) { return x * 2; }); for (int v : view) std::cout << v << " "; // 8 10 12 return 0;}Plegado (fold) con std::accumulate
#include <numeric>
int main() { std::vector<int> nums = {1, 2, 3, 4}; // Suma con valor inicial 0 int sum = std::accumulate(nums.begin(), nums.end(), 0, [](int a, int b) { return a + b; }); // Producto con valor inicial 1 int prod = std::acc