Functional Programming in C++: From Lambdas to Composition
Functional programming (FP) is a paradigm where computations are built on the application of pure functions, data immutability, and the absence of side effects. Although C++ was originally a multi-paradigm language, its modern standards (C++11/14/17/20) have introduced powerful tools for FP: lambda expressions, std::function, STL algorithms, coroutines, and even support for monadic patterns. In this article, we'll explore how to write functional code in C++ without losing performance.
1. Fundamentals of Functional Style: Lambdas and std::function
Lambda expressions are the heart of FP in C++. They allow you to create anonymous functions "on the fly," capturing context.
#include <iostream>#include <vector>#include <algorithm>
int main() { std::vector<int> nums = {1, 2, 3, 4, 5}; // Lambda for filtering even numbers auto is_even = [](int x) { return x % 2 == 0; }; // Usage with std::copy_if algorithm 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;}Key points about lambdas:
- Capture —
[=](by value),[&](by reference),[this](for class methods). - Type — each lambda has a unique type, so
std::functionor templates are used to pass them to functions. - Return value — deduced automatically unless specified otherwise.
std::function — a wrapper for callable objects (functions, lambdas, functors). It's convenient for storage and passing but adds overhead (virtual call).
#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. STL Algorithms as the Foundation of Functional Chains
STL algorithms (std::transform, std::copy_if, std::accumulate, etc.) fit perfectly into the functional style. They do not modify containers directly but take iterators and return results.
Example: Filtering and transformation (map/filter)
#include <numeric>#include <ranges> // C++20
int main() { std::vector<int> data = {1, 2, 3, 4, 5, 6}; // Classic approach (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; }); // Modern approach with 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 with std::accumulate
#include <numeric>
int main() { std::vector<int> nums = {1, 2, 3, 4}; // Sum with initial value 0 int sum = std::accumulate(nums.begin(), nums.end(), 0, [](int a, int b) { return a + b; }); // Product with initial value 1 int prod = std::acc