Programação Funcional em C++: de Lambdas à Composição
Programação Funcional (PF) é um paradigma no qual os cálculos são construídos com base na aplicação de funções puras, imutabilidade de dados e ausência de efeitos colaterais. Embora C++ tenha sido originalmente uma linguagem multiparadigma, seus padrões modernos (C++11/14/17/20) introduziram ferramentas poderosas para PF: expressões lambda, std::function, algoritmos STL, corrotinas e até suporte a padrões mônadas. Neste artigo, vamos explorar como escrever código funcional em C++ sem perder desempenho.
1. Fundamentos do Estilo Funcional: Lambdas e std::function
Expressões lambda são o coração da PF em C++. Elas permitem criar funções anônimas "em tempo real", capturando o 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 com o 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;}Pontos-chave das lambdas:
- Captura —
[=](por valor),[&](por referência),[this](para métodos de classe). - Tipo — cada lambda tem um tipo único, portanto, para passá-la a uma função, usa-se
std::functionou templates. - Valor de retorno — é deduzido automaticamente, a menos que especificado de outra forma.
std::function — um wrapper para objetos callable (funções, lambdas, functores). É conveniente para armazenar e passar, mas adiciona overhead (chamada virtual).
#include <functional>
void apply_twice(std::function<int(int)> f, int x) { std::cout << f(f(x)) << std::endl; // Composição}
int main() { auto square = [](int x) { return x * x; }; apply_twice(square, 3); // (3^2)^2 = 81 return 0;}2. Algoritmos STL como Base para Cadeias Funcionais
Os algoritmos STL (std::transform, std::copy_if, std::accumulate, etc.) se encaixam perfeitamente no estilo funcional. Eles não modificam os contêineres diretamente, mas aceitam iteradores e retornam resultados.
Exemplo: filtragem e transformação (map/filter)
#include <numeric>#include <ranges> // C++20
int main() { std::vector<int> data = {1, 2, 3, 4, 5, 6}; // Abordagem clássica (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; }); // Abordagem moderna com 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;}Dobra (fold) com std::accumulate
#include <numeric>
int main() { std::vector<int> nums = {1, 2, 3, 4}; // Soma com valor inicial 0 int sum = std::accumulate(nums.begin(), nums.end(), 0, [](int a, int b) { return a + b; }); // Produto com valor inicial 1 int prod = std::acc
```