Funktionale Programmierung in C++: Von Lambdas zur Komposition
Funktionale Programmierung (FP) ist ein Paradigma, bei dem Berechnungen auf der Anwendung reiner Funktionen, Datenunveränderlichkeit und der Abwesenheit von Seiteneffekten basieren. Obwohl C++ ursprünglich eine multiparadigmatische Sprache war, haben seine modernen Standards (C++11/14/17/20) leistungsstarke Werkzeuge für FP eingeführt: Lambda-Ausdrücke, std::function, STL-Algorithmen, Coroutinen und sogar Unterstützung für monadische Muster. In diesem Artikel werden wir untersuchen, wie man funktionalen Code in C++ schreibt, ohne Leistung einzubüßen.
1. Grundlagen des funktionalen Stils: Lambdas und std::function
Lambda-Ausdrücke sind das Herzstück von FP in C++. Sie ermöglichen es, anonyme Funktionen "im Handumdrehen" zu erstellen, wobei der Kontext erfasst wird.
#include <iostream>#include <vector>#include <algorithm>
int main() { std::vector<int> nums = {1, 2, 3, 4, 5}; // Lambda zum Filtern gerader Zahlen auto is_even = [](int x) { return x % 2 == 0; }; // Verwendung mit dem std::copy_if-Algorithmus 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;}Wichtige Punkte zu Lambdas:
- Capture —
[=](by value),[&](by reference),[this](für Klassenmethoden). - Typ — jedes Lambda hat einen eindeutigen Typ, daher werden
std::functionoder Templates verwendet, um sie an Funktionen zu übergeben. - Rückgabewert — wird automatisch abgeleitet, sofern nicht anders angegeben.
std::function — ein Wrapper für aufrufbare Objekte (Funktionen, Lambdas, Funktoren). Es ist praktisch für die Speicherung und Übergabe, fügt aber Overhead hinzu (virtueller Aufruf).
#include <functional>
void apply_twice(std::function<int(int)> f, int x) { std::cout << f(f(x)) << std::endl; // Komposition}
int main() { auto square = [](int x) { return x * x; }; apply_twice(square, 3); // (3^2)^2 = 81 return 0;}2. STL-Algorithmen als Grundlage funktionaler Ketten
STL-Algorithmen (std::transform, std::copy_if, std::accumulate usw.) passen perfekt in den funktionalen Stil. Sie modifizieren Container nicht direkt, sondern nehmen Iteratoren entgegen und geben Ergebnisse zurück.
Beispiel: Filtern und Transformation (map/filter)
#include <numeric>#include <ranges> // C++20
int main() { std::vector<int> data = {1, 2, 3, 4, 5, 6}; // Klassischer Ansatz (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; }); // Moderner Ansatz mit 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;}Fold mit std::accumulate
#include <numeric>
int main() { std::vector<int> nums = {1, 2, 3, 4}; // Summe mit Startwert 0 int sum = std::accumulate(nums.begin(), nums.end(), 0, [](int a, int b) { return a + b; }); // Produkt mit Startwert 1 int prod = std::acc