Functional Programming in C++: Advanced Level
\n\nFunctional programming (FP) is a paradigm that treats computation as the evaluation of mathematical functions and avoids changing state and mutable data. Although C++ is traditionally associated with object-oriented and procedural programming, modern standards (C++11 and later) have introduced powerful tools for writing functional code. In this article, we will go beyond basic lambdas and explore advanced techniques: from function composition to emulating monads.
\n\n1. Function Composition and Pipelines
\n\nFunction composition is the process of combining two or more functions to create a new function. In C++, lambda expressions and templates are convenient for this purpose.
\n\n#include <iostream>\n#include <functional>\n#include <vector>\n#include <algorithm>\n\n// Template for composing two functions\ntemplate<typename F, typename G>\nauto compose(F f, G g) {\n return [f, g](auto x) {\n return f(g(x));\n };\n}\n\n// Usage example\nint main() {\n auto addTwo = [](int x) { return x + 2; };\n auto multiplyByThree = [](int x) { return x * 3; };\n\n // Composition: first multiplyByThree, then addTwo\n auto composed = compose(addTwo, multiplyByThree);\n\n std::cout << composed(5) << std::endl; // (5*3) + 2 = 17\n\n // Pipeline processing using std::ranges (C++20)\n std::vector<int> numbers = {1, 2, 3, 4, 5};\n auto result = numbers\n | std::views::filter([](int n) { return n % 2 == 0; })\n | std::views::transform([](int n) { return n * n; });\n\n for (int n : result) {\n std::cout << n << " "; // 4 16\n }\n return 0;\n}\n\n\nPipelines with std::views (C++20) allow building transformation chains without creating intermediate containers, which is a classic functional approach.
2. Currying and Partial Application
\n\nCurrying is the transformation of a function that takes multiple arguments into a sequence of functions, each taking a single argument. Partial application fixes some of the arguments.
\n\n#include <iostream>\n#include <functional>\n\n// Curried addition function\nauto curriedAdd = [](int a) {\n return [a](int b) {\n return [a, b](int c) {\n return a + b + c;\n };\n };\n};\n\n// Partial application using std::bind\nint multiply(int a, int b, int c) {\n return a * b * c;\n}\n\nint main() {\n // Using currying\n auto add5 = curriedAdd(5);\n auto add5And3 = add5(3);\n std::cout << add5And3(2) << std::endl; // 10\n\n // Partial application via std::bind\n using namespace std::placeholders;\n auto multiplyBy2 = std::bind(multiply, _1, _2, 2);\n std::cout << multiplyBy2(3, 4) << std::endl; // 3*4*2 = 24\n\n // Partial application via lambda (more idiomatic in C++14+)\n auto multiplyBy3 = [](int a, int b) { return multiply(a, b, 3); };\n std::cout << multiplyBy3(5, 6) << std::endl; // 5*6*3 = 90\n\n return 0;\n}\n\n\nCurrying is especially useful when working with higher-order functions such as std::transform or std::accumulate, allowing you to create specialized versions of functions "on the fly."
3. Functors, Applicatives, and Emulating Monads
\n\nIn Haskell, monads are a powerful pattern for handling side effects. In C++, we can emulate them using class templates. Let's consider a simple Maybe monad (analogous to std::optional), but with monadic operations.
#include <iostream>\n#include <functional>\n#include <optional>\n\n// Maybe monad (wrapper over std::optional)\ntemplate<typename T>\nclass Maybe {\n s