Functional Programming in C++: Advanced Level
Functional Programming (FP) is a paradigm in which computations are represented as the evaluation of mathematical functions, avoiding state changes and mutable data. Although C++ was originally an object-oriented and procedural language, starting with the C++11 standard it gained powerful tools for FP: lambda expressions, std::function, std::bind, and C++17 added support for std::optional and std::variant, which allow implementing monadic patterns. In this article, we will look at advanced functional programming techniques in C++: function composition, currying, working with monads, and creating pure functions using modern language features.
1. Lambda Expressions and Pure Functions
Lambda expressions are the foundation of the functional style in C++. They allow you to create anonymous functions on the spot, which is ideal for algorithms from <algorithm>. However, for true FP, it is important that lambdas are pure — that is, they have no side effects and do not depend on external mutable state.
#include <iostream>#include <vector>#include <algorithm>
int main() { std::vector<int> numbers = {1, 2, 3, 4, 5};
// Pure lambda: only input data, only output auto square = [](int x) { return x * x; };
// Apply transform without side effects std::vector<int> squared; std::transform(numbers.begin(), numbers.end(), std::back_inserter(squared), square);
for (int n : squared) std::cout << n << " "; // 1 4 9 16 25 return 0;}For advanced use, it is worth avoiding capture by reference ([&]) unless critical. Capture by value ([=]) or explicit enumeration of captures makes the lambda more predictable and testable.
2. Function Composition with std::function and Operators
Composition is combining two or more functions into one. In C++, composition can be implemented using templates and std::function. Let's look at a generalized composer:
#include <functional>#include <iostream>
template <typename F, typename G>auto compose(F f, G g) { return [f, g](auto x) { return f(g(x)); };}
int main() { auto addOne = [](int x) { return x + 1; }; auto multiplyByTwo = [](int x) { return x * 2; };
auto addThenMultiply = compose(multiplyByTwo, addOne); std::cout << addThenMultiply(5) << std::endl; // (5+1)*2 = 12
// Composition of multiple functions auto triple = compose(multiplyByTwo, compose(addOne, addOne)); std::cout << triple(5) << std::endl; // ((5+1)+1)*2 = 14 return 0;}For more complex chains, you can use a pipeline operator (similar to |> in other languages). In C++20, it became possible to overload operator| to create a convenient syntax:
#include <functional>#include <iostream>
template <typename F>class Pipeable { F f;public: Pipeable(F func) : f(func) {} template <typename T> auto operator()(T x) const { return f(x); }};
template <typename T, typename F>auto operator|(T x, Pipeable<F> p) { return p(x);}
int main() { auto addOne = Pipeable([](int x) { return x + 1; }); auto square = Pipeable([](int x) { return x * x; });
int result = 5 | addOne | square; // (5+1)^2 = 36 std::cout << result << std::endl; return 0;}This approach makes the code declarative and easy to read.
3. Currying and Partial Application
Currying is transforming a function of several arguments into