Programmation fonctionnelle en C++ : des lambdas à la composition
La programmation fonctionnelle (PF) est un paradigme dans lequel les calculs sont construits sur l'application de fonctions pures, l'immuabilité des données et l'absence d'effets secondaires. Bien que C++ ait été initialement un langage multi-paradigme, ses normes modernes (C++11/14/17/20) ont introduit des outils puissants pour la PF : expressions lambda, std::function, algorithmes STL, coroutines et même le support de motifs monadiques. Dans cet article, nous allons voir comment écrire du code fonctionnel en C++ sans perdre en performance.
1. Fondamentaux du style fonctionnel : lambdas et std::function
Les expressions lambda sont le cœur de la PF en C++. Elles permettent de créer des fonctions anonymes « à la volée », en capturant le contexte.
#include <iostream>#include <vector>#include <algorithm>
int main() { std::vector<int> nums = {1, 2, 3, 4, 5}; // Lambda pour filtrer les nombres pairs auto is_even = [](int x) { return x % 2 == 0; }; // Utilisation avec l'algorithme 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;}Points clés des lambdas :
- Capture —
[=](par valeur),[&](par référence),[this](pour les méthodes de classe). - Type — chaque lambda a un type unique, donc pour la passer à une fonction on utilise
std::functionou des templates. - Valeur de retour — déduite automatiquement, sauf indication contraire.
std::function — une enveloppe pour les objets callable (fonctions, lambdas, foncteurs). Pratique pour le stockage et le passage, mais ajoute une surcharge (appel virtuel).
#include <functional>
void apply_twice(std::function<int(int)> f, int x) { std::cout << f(f(x)) << std::endl; // Composition}
int main() { auto square = [](int x) { return x * x; }; apply_twice(square, 3); // (3^2)^2 = 81 return 0;}2. Algorithmes STL comme base des chaînes fonctionnelles
Les algorithmes STL (std::transform, std::copy_if, std::accumulate, etc.) s'intègrent parfaitement au style fonctionnel. Ils ne modifient pas les conteneurs directement, mais prennent des itérateurs et retournent des résultats.
Exemple : filtrage et transformation (map/filter)
#include <numeric>#include <ranges> // C++20
int main() { std::vector<int> data = {1, 2, 3, 4, 5, 6}; // Approche classique (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; }); // Approche moderne avec 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;}Plissement (fold) avec std::accumulate
#include <numeric>
int main() { std::vector<int> nums = {1, 2, 3, 4}; // Somme avec valeur initiale 0 int sum = std::accumulate(nums.begin(), nums.end(), 0, [](int a, int b) { return a + b; }); // Produit avec valeur initiale 1 int prod = std::acc