0% found this document useful (0 votes)
2 views26 pages

Functional Programming

The document is a comprehensive tutorial on functional programming in C++, covering concepts such as functions as arguments, function pointers, lambda expressions, and higher-order functions. It discusses core principles of functional programming and provides examples of using templates, functors, and standard algorithms like map, filter, and reduce. The tutorial is designed for C++11 and later versions, offering insights into modern C++ features and best practices.

Uploaded by

hashir raheem
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
2 views26 pages

Functional Programming

The document is a comprehensive tutorial on functional programming in C++, covering concepts such as functions as arguments, function pointers, lambda expressions, and higher-order functions. It discusses core principles of functional programming and provides examples of using templates, functors, and standard algorithms like map, filter, and reduce. The tutorial is designed for C++11 and later versions, offering insights into modern C++ features and best practices.

Uploaded by

hashir raheem
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Functional Programming

in C++

Functions as Arguments • Function Objects


Function Pointers • Lambda Expressions
Capture Lists • Map, Filter & Reduce

A Comprehensive Tutorial for C++11 and Beyond

C++ Tutorial Series

Modern C++11 / C++14 / C++17 / C++20

March 29, 2026


C++ Tutorial Series Functional Programming in C++

Contents
1 Introduction to Functional Programming 3
1.1 Core Principles of Functional Programming . . . . . . . . . . . . . . . 3

2 Functions as Arguments 3
2.1 Why Pass Functions as Arguments? . . . . . . . . . . . . . . . . . . . . 3
2.2 Basic Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
2.3 Generic Version with Templates . . . . . . . . . . . . . . . . . . . . . . 4

3 Function Pointers 5
3.1 Syntax of Function Pointers . . . . . . . . . . . . . . . . . . . . . . . . 5
3.2 Array of Function Pointers . . . . . . . . . . . . . . . . . . . . . . . . . 5
3.3 Using typedef and using for Cleaner Syntax . . . . . . . . . . . . . . . 6

4 Function Objects (Functors) 6


4.1 Basic Functor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
4.2 Stateful Functor — Accumulator . . . . . . . . . . . . . . . . . . . . . 7
4.3 Comparison Functor for Sorting . . . . . . . . . . . . . . . . . . . . . . 7
4.4 std::function — Type-Erased Callable Wrapper . . . . . . . . . . . . 8

5 Lambda Expressions 9
5.1 Lambda Syntax . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
5.2 Basic Lambda Examples . . . . . . . . . . . . . . . . . . . . . . . . . . 9
5.3 Mutable Lambdas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
5.4 Generic Lambdas (C++14) . . . . . . . . . . . . . . . . . . . . . . . . 11

6 Capture Lists 11
6.1 Capture Modes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
6.2 Capture by Value vs by Reference . . . . . . . . . . . . . . . . . . . . . 11
6.3 Default Capture with Overrides . . . . . . . . . . . . . . . . . . . . . . 12
6.4 Capturing this in Member Functions . . . . . . . . . . . . . . . . . . . 12
6.5 Recursive Lambda with std::function . . . . . . . . . . . . . . . . . 13

7 Map — std::transform 14
7.1 Unary Transform . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
7.2 Binary Transform . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
7.3 Building a Reusable map Helper . . . . . . . . . . . . . . . . . . . . . . 15

8 Filter — std::copy_if / std::remove_if 15


8.1 Using std::copy_if . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
8.2 Using std::remove_if (Erase-Remove Idiom) . . . . . . . . . . . . . . 16
8.3 Building a Reusable filter Helper . . . . . . . . . . . . . . . . . . . . 17

9 Reduce — std::accumulate / std::reduce 17


9.1 Using std::accumulate . . . . . . . . . . . . . . . . . . . . . . . . . . 17
9.2 Computing Statistics with Reduce . . . . . . . . . . . . . . . . . . . . . 18
9.3 std::reduce (C++17) — Parallel-Friendly . . . . . . . . . . . . . . . 19
9.4 Building a Reusable reduce Helper . . . . . . . . . . . . . . . . . . . . 19

1
C++ Tutorial Series Functional Programming in C++

10 Composing Map, Filter, and Reduce 20


10.1 Complete Pipeline Example . . . . . . . . . . . . . . . . . . . . . . . . 20

11 Advanced Topics 21
11.1 Partial Application with std::bind . . . . . . . . . . . . . . . . . . . . 21
11.2 Function Composition . . . . . . . . . . . . . . . . . . . . . . . . . . . 22

12 Summary and Best Practices 23


12.1 Quick Reference Table . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
12.2 Best Practices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23

Further Reading 25

2
C++ Tutorial Series Functional Programming in C++

1 Introduction to Functional Programming


Functional programming (FP) is a programming paradigm that treats computation
as the evaluation of mathematical functions and avoids changing state and mutable
data. Although C++ is primarily an object-oriented and procedural language, it has
incorporated powerful functional-programming features over its evolution—especially
since the C++11 standard.

1.1 Core Principles of Functional Programming


• Pure Functions: Functions that always produce the same output for the same
input and have no side effects.
• First-Class Functions: Functions are treated as values—they can be stored in
variables, passed as arguments, and returned from other functions.
• Higher-Order Functions: Functions that take other functions as parameters or
return functions.
• Immutability: Data is not modified after creation; new data is produced instead.
• Function Composition: Building complex functions by combining simpler ones.

Note
C++ supports functional programming through: function pointers,
std::function, callable objects (functors), lambda expressions, and the
<algorithm> and <numeric> headers.

2 Functions as Arguments
Passing functions as arguments is the cornerstone of higher-order programming. It lets
us write generic algorithms that can be customised at the call site without modifying
the algorithm itself.

2.1 Why Pass Functions as Arguments?


• Separation of concerns: Algorithms stay generic; behaviour is injected.
• Code reuse: One algorithm, many behaviours.
• Composability: Combine small building blocks into complex pipelines.

2.2 Basic Example


1 # include <iostream >
2 # include <vector >
3 # include <algorithm >
4
5 // A simple predicate function
6 bool isEven (int n) {
7 return n % 2 == 0;
8 }

3
C++ Tutorial Series Functional Programming in C++

9
10 bool isOdd (int n) {
11 return n % 2 != 0;
12 }
13
14 // A generic " count_if_custom " function that accepts another function
15 int countIf ( const std :: vector <int >& v, bool (* predicate )(int)) {
16 int count = 0;
17 for (int x : v) {
18 if ( predicate (x)) ++ count ;
19 }
20 return count ;
21 }
22

23 int main () {
24 std :: vector <int > numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
25
26 std :: cout << "Even count : " << countIf (numbers , isEven ) << "\n"; //
5
27 std :: cout << "Odd count : " << countIf (numbers , isOdd ) << "\n"; //
5
28
29 return 0;
30 }

Listing 1: Passing a function as an argument

2.3 Generic Version with Templates


Using templates, we can accept any callable — a function pointer, a functor, or a
lambda — without specifying its type explicitly:
1 # include <iostream >
2 # include <vector >
3
4 // Template : accepts any callable type F
5 template <typename F>
6 void applyToAll ( const std :: vector <int >& v, F func) {
7 for (int x : v) {
8 func(x);
9 }
10 }
11
12 void printDouble (int x) {
13 std :: cout << x * 2 << " ";
14 }
15
16 int main () {
17 std :: vector <int > nums = {1, 2, 3, 4, 5};
18 applyToAll (nums , printDouble ); // Output : 2 4 6 8 10
19 std :: cout << "\n";
20 return 0;
21 }

Listing 2: Template-based higher-order function

4
C++ Tutorial Series Functional Programming in C++

3 Function Pointers
A function pointer is a variable that stores the address of a function. It allows calling
a function indirectly through a pointer.

3.1 Syntax of Function Pointers


The general syntax for declaring a function pointer is:
return_type (*pointer_name)(parameter_types);
1 # include <iostream >
2
3 int add(int a, int b) { return a + b; }
4 int subtract (int a, int b) { return a - b; }
5 int multiply (int a, int b) { return a * b; }
6
7 int main () {
8 // Declare a function pointer
9 int (* operation )(int , int);
10

11 // Assign and call


12 operation = add;
13 std :: cout << "add (3 ,4) = " << operation (3, 4) << "\n"; // 7
14
15 operation = subtract ;
16 std :: cout << " subtract (3 ,4) = " << operation (3, 4) << "\n"; // -1
17
18 operation = multiply ;
19 std :: cout << " multiply (3 ,4) = " << operation (3, 4) << "\n"; // 12
20
21 return 0;
22 }

Listing 3: Function pointer declaration and usage

3.2 Array of Function Pointers


1 # include <iostream >
2 # include <string >
3

4 int add(int a, int b) { return a + b; }


5 int sub(int a, int b) { return a - b; }
6 int mul(int a, int b) { return a * b; }
7 int div_(int a, int b) { return (b != 0) ? a / b : 0; }
8
9 int main () {
10 // Array of function pointers
11 int (* ops [4])(int , int) = {add , sub , mul , div_ };
12 std :: string names [4] = {"add", "sub", "mul", "div"};
13
14 int a = 12, b = 4;
15 for (int i = 0; i < 4; ++i) {
16 std :: cout << names [i] << "(" << a << ", " << b << ") = "
17 << ops[i](a, b) << "\n";
18 }

5
C++ Tutorial Series Functional Programming in C++

19 return 0;
20 }

Listing 4: Array of function pointers — dispatch table

3.3 Using typedef and using for Cleaner Syntax


1 # include <iostream >
2
3 // C-style typedef
4 typedef int (* BinaryOp )(int , int);
5
6 // Modern C++ using alias ( preferred )
7 using UnaryOp = int (*)(int);
8
9 int square (int x) { return x * x; }
10 int doubleit (int x) { return x * 2; }
11 int applyBinary (int a, int b, BinaryOp op) { return op(a, b); }
12
13 int main () {
14 UnaryOp f = square ;
15 std :: cout << " square (5) = " << f(5) << "\n"; // 25
16

17 f = doubleit ;
18 std :: cout << " double (5) = " << f(5) << "\n"; // 10
19
20 // Using the typedef alias
21 BinaryOp myOp = []( int a, int b) -> int { return a + b; };
22 std :: cout << " lambda (3 ,4) = " << myOp (3, 4) << "\n"; // 7
23
24 return 0;
25 }

Listing 5: Cleaner function pointer syntax with typedef / using

Note
Function pointers cannot capture state. For stateful callables, use functors or lamb-
das. Also, function pointers have a slight indirection overhead compared to direct
calls—compilers often inline direct function calls but cannot always inline through
a pointer.

4 Function Objects (Functors)


A functor (or function object) is any object of a class that overloads operator().
Functors can carry state, making them more powerful than plain function pointers.

4.1 Basic Functor


1 # include <iostream >
2
3 struct Multiplier {
4 int factor ; // state !
5

6
C++ Tutorial Series Functional Programming in C++

6 Multiplier (int f) : factor (f) {}


7

8 int operator ()(int x) const {


9 return x * factor ;
10 }
11 };
12
13 int main () {
14 Multiplier timesThree (3);
15 Multiplier timesTen (10);
16
17 std :: cout << "3 * 7 = " << timesThree (7) << "\n"; // 21
18 std :: cout << "10 * 7 = " << timesTen (7) << "\n"; // 70
19

20 return 0;
21 }

Listing 6: A simple functor

4.2 Stateful Functor — Accumulator


1 # include <iostream >
2 # include <vector >
3 # include <algorithm >
4
5 struct Accumulator {
6 int total = 0;
7

8 void operator ()(int x) {


9 total += x;
10 }
11 };
12
13 int main () {
14 std :: vector <int > v = {1, 2, 3, 4, 5};
15
16 Accumulator acc;
17 // std :: for_each returns the functor by value in C++11
18 acc = std :: for_each (v. begin () , [Link] () , acc);
19

20 std :: cout << "Sum = " << acc. total << "\n"; // 15
21 return 0;
22 }

Listing 7: Stateful functor example

4.3 Comparison Functor for Sorting


1 # include <iostream >
2 # include <vector >
3 # include <algorithm >
4 # include <string >
5
6 struct LengthLess {
7 bool operator ()( const std :: string & a, const std :: string & b) const {
8 return a. length () < b. length ();

7
C++ Tutorial Series Functional Programming in C++

9 }
10 };
11
12 int main () {
13 std :: vector <std :: string > words = {" banana ", "kiwi", "fig",
14 " apple ", " elderberry "};
15
16 std :: sort( words . begin () , words .end () , LengthLess {});
17
18 for ( const auto& w : words )
19 std :: cout << w << "\n";
20 // fig , kiwi , apple , banana , elderberry
21
22 return 0;
23 }

Listing 8: Custom comparator functor

4.4 std::function — Type-Erased Callable Wrapper


std::function (from <functional>) is a polymorphic wrapper that can hold any
callable with a given signature: function pointers, lambdas, functors, or std::bind
results.
1 # include <iostream >
2 # include <functional >
3 # include <vector >
4
5 // A free function
6 int square (int x) { return x * x; }
7
8 // A functor
9 struct Cube {
10 int operator ()(int x) const { return x * x * x; }
11 };
12
13 int main () {
14 std :: function <int(int)> f;
15
16 f = square ; // function pointer
17 std :: cout << " square (4) = " << f(4) << "\n"; // 16
18
19 f = Cube {}; // functor
20 std :: cout << "cube (4) = " << f(4) << "\n"; // 64
21
22 f = []( int x) { return x + 100; }; // lambda
23 std :: cout << " lambda (4) = " << f(4) << "\n"; // 104
24
25 // Store in a vector !
26 std :: vector <std :: function <int(int)>> transforms = {
27 square ,
28 Cube {},
29 []( int x) { return -x; }
30 };
31
32 for (auto& fn : transforms )
33 std :: cout << fn (5) << " ";

8
C++ Tutorial Series Functional Programming in C++

34 std :: cout << "\n"; // 25 125 -5


35

36 return 0;
37 }

Listing 9: std::function usage

Tip
Prefer auto or templates over std::function in performance-critical code.
std::function has a small overhead due to type erasure and potential heap al-
location. Use it when you need to store callables in containers or pass them across
ABI boundaries.

5 Lambda Expressions
Introduced in C++11, lambda expressions allow you to define anonymous function
objects inline. They are syntactic sugar for automatically generated functors.

5.1 Lambda Syntax


1 [ capture_list ] ( parameters ) -> return_type {
2 body
3 }

Listing 10: General lambda syntax (not compilable alone)

Each component:
• capture_list — variables from the enclosing scope to bring in.
• parameters — input arguments (like a normal function).
• return_type — optional; often deduced by the compiler.
• body — the function body.

5.2 Basic Lambda Examples


1 # include <iostream >
2 # include <vector >
3 # include <algorithm >
4
5 int main () {
6 // 1. Lambda stored in a variable
7 auto greet = []() {
8 std :: cout << "Hello from a lambda !\n";
9 };
10 greet ();
11
12 // 2. Lambda with parameters
13 auto add = []( int a, int b) {
14 return a + b;
15 };

9
C++ Tutorial Series Functional Programming in C++

16 std :: cout << "add (3, 4) = " << add (3, 4) << "\n"; // 7
17

18 // 3. Lambda with explicit return type


19 auto divide = []( double a, double b) -> double {
20 if (b == 0.0) return 0.0;
21 return a / b;
22 };
23 std :: cout << "10 / 3 = " << divide (10.0 , 3.0) << "\n";
24
25 // 4. Immediately - invoked lambda
26 int result = []( int x) { return x * x; }(7);
27 std :: cout << "7 * 7 = " << result << "\n"; // 49
28
29 // 5. Lambda in std :: sort
30 std :: vector <int > v = {5, 2, 8, 1, 9, 3};
31 std :: sort(v. begin () , [Link] () ,
32 []( int a, int b) { return a > b; }); // descending
33
34 for (int x : v) std :: cout << x << " ";
35 std :: cout << "\n"; // 9 8 5 3 2 1
36
37 return 0;
38 }

Listing 11: Simple lambda expressions

5.3 Mutable Lambdas


By default, a lambda’s operator() is const. The mutable keyword removes this
restriction, allowing the lambda to modify its captured-by-value copies:
1 # include <iostream >
2

3 int main () {
4 int counter = 0;
5
6 // Without mutable : compile error if we tried to modify counter
copy
7 auto increment = [ counter ]() mutable {
8 ++ counter ; // modifies the COPY inside the lambda
9 std :: cout << " Inside lambda : " << counter << "\n";
10 };
11
12 increment (); // Inside lambda : 1
13 increment (); // Inside lambda : 2
14 increment (); // Inside lambda : 3
15
16 std :: cout << " Original counter : " << counter << "\n"; // Still 0!
17 return 0;
18 }

Listing 12: Mutable lambda

10
C++ Tutorial Series Functional Programming in C++

5.4 Generic Lambdas (C++14)


C++14 introduced auto parameters in lambdas, effectively making them function tem-
plates:
1 # include <iostream >
2 # include <string >
3
4 int main () {
5 // Generic lambda — works on any type supporting operator +
6 auto add = []( auto a, auto b) {
7 return a + b;
8 };
9
10 std :: cout << add (3, 4) << "\n"; // 7 (int)
11 std :: cout << add (3.14 , 2.86) << "\n"; // 6.0 ( double )
12 std :: cout << add(std :: string ("Hello , "), std :: string ("World !"))
13 << "\n"; // Hello , World !
14
15 // Generic comparator
16 auto less = []( auto a, auto b) { return a < b; };
17 std :: cout << std :: boolalpha
18 << less (3, 5) << "\n" // true
19 << less('z','a') << "\n"; // false
20 return 0;
21 }

Listing 13: Generic lambdas with auto parameters (C++14)

6 Capture Lists
The capture list is the distinguishing feature of lambdas over plain function pointers.
It specifies which variables from the enclosing scope are accessible inside the lambda
body.

6.1 Capture Modes


Syntax Meaning
[] Capture nothing
[=] Capture all used variables by value (copy)
[&] Capture all used variables by reference
[x] Capture x by value
[&x] Capture x by reference
[=, &x] Capture everything by value, but x by reference
[&, x] Capture everything by reference, but x by value
[this] Capture the current object pointer
[*this] Capture the current object by value (C++17)

6.2 Capture by Value vs by Reference


1 # include <iostream >
2

11
C++ Tutorial Series Functional Programming in C++

3 int main () {
4 int x = 10;
5 int y = 20;
6
7 // �� Capture by VALUE �����������������������������������������
8 auto byValue = [x, y]() {
9 std :: cout << "by value : x=" << x << " y=" << y << "\n";
10 // x = 99; // ERROR : x is const inside (use mutable to allow )
11 };
12
13 // �� Capture by REFERENCE ��������������������������������������
14 auto byRef = [&x, &y]() {
15 x *= 2; // modifies the original x!
16 y *= 2;
17 std :: cout << "by ref: x=" << x << " y=" << y << "\n";
18 };
19
20 x = 50; y = 60; // Change AFTER lambda definition
21

22 byValue (); // by value : x=50 y=60 ( captures current values )


23 byRef (); // by ref : x=100 y=120 (sees updated values !)
24
25 std :: cout << " After byRef : x=" << x << " y=" << y << "\n";
26 // x=100 , y=120 — originals were modified !
27

28 return 0;
29 }

Listing 14: Capture by value vs. by reference

6.3 Default Capture with Overrides


1 # include <iostream >
2

3 int main () {
4 int a = 1, b = 2, c = 3, d = 4;
5
6 // Capture all by value , but d by reference
7 auto mixed = [=, &d]() {
8 // a, b, c are copies — read -only by default
9 // d is a reference — can be modified
10 d = a + b + c; // d = 6
11 std :: cout << "a=" << a << " b=" << b
12 << " c=" << c << " d=" << d << "\n";
13 };
14

15 mixed ();
16 std :: cout << "d after lambda : " << d << "\n"; // 6
17 return 0;
18 }

Listing 15: Mixed captures: default + specific override

6.4 Capturing this in Member Functions


1 # include <iostream >

12
C++ Tutorial Series Functional Programming in C++

2 # include <vector >


3 # include <algorithm >
4
5 class Filter {
6 int threshold ;
7 public :
8 Filter (int t) : threshold (t) {}
9

10 // Lambda captures 'this ' to access member variable


11 std :: vector <int > above ( const std :: vector <int >& data) const {
12 std :: vector <int > result ;
13 std :: copy_if (data. begin () , [Link] () ,
14 std :: back_inserter ( result ),
15 [this ]( int x) { return x > threshold ; });
16 return result ;
17 }
18 };
19
20 int main () {
21 Filter f(5);
22 std :: vector <int > nums = {1, 3, 5, 7, 9, 2, 6, 8};
23 auto res = [Link] (nums);
24 for (int v : res) std :: cout << v << " "; // 7 9 6 8
25 std :: cout << "\n";
26 return 0;
27 }

Listing 16: Capturing this in a class method

6.5 Recursive Lambda with std::function


Because a lambda’s type is anonymous, a recursive lambda needs either std::function
or a self-referencing trick:
1 # include <iostream >
2 # include <functional >
3
4 int main () {
5 // Recursive lambda via std :: function
6 std :: function <int(int)> factorial = [& factorial ]( int n) -> int {
7 return (n <= 1) ? 1 : n * factorial (n - 1);
8 };
9
10 for (int i = 0; i <= 7; ++i)
11 std :: cout << i << "! = " << factorial (i) << "\n";
12
13 return 0;
14 }

Listing 17: Recursive lambda using std::function

Note
Be cautious with default capture-by-reference ([&]) when the lambda outlives the
enclosing scope (e.g., stored in a container or passed to a thread). The referenced
variables may be destroyed before the lambda is called—this is undefined behaviour.

13
C++ Tutorial Series Functional Programming in C++

7 Map — std::transform
The map operation applies a function to every element of a collection and produces
a new collection of the same size. In C++, this is implemented by std::transform
from <algorithm>.

7.1 Unary Transform


1 # include <iostream >
2 # include <vector >
3 # include <algorithm >
4 # include <string >
5 # include <cctype >
6
7 int main () {
8 // �� Example 1: Square each element ���������������������������
9 std :: vector <int > numbers = {1, 2, 3, 4, 5};
10 std :: vector <int > squares ( numbers .size ());
11

12 std :: transform ( numbers . begin () , numbers .end () ,


13 squares . begin () ,
14 []( int x) { return x * x; });
15
16 for (int s : squares ) std :: cout << s << " "; // 1 4 9 16 25
17 std :: cout << "\n";
18
19 // �� Example 2: Convert string to uppercase ��������������������
20 std :: string text = "hello , functional world !";
21 std :: string upper ([Link] () , ' ');
22
23 std :: transform (text. begin () , [Link] () , upper . begin () ,
24 []( char c) { return std :: toupper (c); });
25
26 std :: cout << upper << "\n";
27 // HELLO , FUNCTIONAL WORLD !
28
29 // �� Example 3: Transform in - place ����������������������������
30 std :: vector <double > prices = {100.0 , 250.5 , 89.99 , 45.0};
31 // Apply 10% discount
32 std :: transform ( prices . begin () , prices .end () ,
33 prices . begin () , // output = input (in - place )
34 []( double p) { return p * 0.90; });
35

36 for ( double p : prices ) std :: cout << p << " ";


37 std :: cout << "\n";
38
39 return 0;
40 }

Listing 18: std::transform — unary map

7.2 Binary Transform


std::transform also supports a binary version that combines two ranges element-wise:
1 # include <iostream >

14
C++ Tutorial Series Functional Programming in C++

2 # include <vector >


3 # include <algorithm >
4
5 int main () {
6 std :: vector <int > a = {1, 2, 3, 4, 5};
7 std :: vector <int > b = {10 , 20, 30, 40, 50};
8 std :: vector <int > c([Link] ());
9

10 // Element -wise multiplication : c[i] = a[i] * b[i]


11 std :: transform (a. begin () , [Link] () , b. begin () ,
12 c. begin () ,
13 []( int x, int y) { return x * y; });
14
15 for (int v : c) std :: cout << v << " "; // 10 40 90 160 250
16 std :: cout << "\n";
17
18 return 0;
19 }

Listing 19: std::transform — binary (zip) operation

7.3 Building a Reusable map Helper


1 # include <iostream >
2 # include <vector >
3 # include <algorithm >
4
5 // Functional - style map: returns a new vector
6 template <typename T, typename F>
7 auto map( const std :: vector <T >& v, F func) {
8 using R = decltype (func(v[0]));
9 std :: vector <R> result ([Link] ());
10 std :: transform (v. begin () , [Link] () , result . begin () , func);
11 return result ;
12 }
13
14 int main () {
15 std :: vector <int > nums = {1, 2, 3, 4, 5};
16 auto doubled = map(nums , []( int x) { return x * 2; });
17 auto asDouble = map(nums , []( int x) { return static_cast <double >(x)
; });
18
19 for (int v : doubled ) std :: cout << v << " "; // 2 4 6 8 10
20 std :: cout << "\n";
21 for ( double v : asDouble ) std :: cout << v << " "; // 1 2 3 4 5
22 std :: cout << "\n";
23 return 0;
24 }

Listing 20: Generic map helper function

8 Filter — std::copy_if / std::remove_if


The filter operation selects elements from a collection that satisfy a predicate, produc-
ing a (potentially smaller) collection.

15
C++ Tutorial Series Functional Programming in C++

8.1 Using std::copy_if


1 # include <iostream >
2 # include <vector >
3 # include <algorithm >
4 # include <iterator >
5 # include <string >
6
7 int main () {
8 // �� Example 1: Filter even numbers ���������������������������
9 std :: vector <int > nums = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
10 std :: vector <int > evens ;
11
12 std :: copy_if (nums. begin () , [Link] () ,
13 std :: back_inserter ( evens ),
14 []( int x) { return x % 2 == 0; });
15

16 for (int v : evens ) std :: cout << v << " "; // 2 4 6 8 10


17 std :: cout << "\n";
18
19 // �� Example 2: Filter strings by length �����������������������
20 std :: vector <std :: string > words =
21 {"hi", "hello ", "hey", " greetings ", " howdy ", "yo"};
22 std :: vector <std :: string > longWords ;
23
24 std :: copy_if (words . begin () , words .end () ,
25 std :: back_inserter ( longWords ),
26 []( const std :: string & s) { return s. length () > 3; });
27

28 for ( const auto& w : longWords )


29 std :: cout << w << " "; // hello greetings howdy
30 std :: cout << "\n";
31
32 return 0;
33 }

Listing 21: std::copy_if — filter elements into a new container

8.2 Using std::remove_if (Erase-Remove Idiom)


1 # include <iostream >
2 # include <vector >
3 # include <algorithm >
4
5 int main () {
6 std :: vector <int > v = {1, -3, 4, -1, 5, -9, 2, 6};
7
8 // Remove all negative numbers in - place
9 // Step 1: std :: remove_if moves "bad" elements to the end ,
10 // returns iterator to new logical end
11 auto newEnd = std :: remove_if (v. begin () , [Link] () ,
12 []( int x) { return x < 0; });
13
14 // Step 2: erase the " graveyard " elements
15 v. erase (newEnd , [Link] ());
16
17 for (int x : v) std :: cout << x << " "; // 1 4 5 2 6
18 std :: cout << "\n";

16
C++ Tutorial Series Functional Programming in C++

19
20 return 0;
21 }

Listing 22: Erase-remove idiom — modify container in-place

8.3 Building a Reusable filter Helper


1 # include <iostream >
2 # include <vector >
3 # include <algorithm >
4 # include <iterator >
5
6 template <typename T, typename Pred >
7 std :: vector <T> filter ( const std :: vector <T >& v, Pred pred) {
8 std :: vector <T> result ;
9 std :: copy_if (v. begin () , [Link] () ,
10 std :: back_inserter ( result ), pred);
11 return result ;
12 }
13
14 int main () {
15 std :: vector <int > nums = {-5, -3, 0, 1, 4, 7, -2, 9};
16
17 auto positives = filter (nums , []( int x) { return x > 0; });
18 auto negatives = filter (nums , []( int x) { return x < 0; });
19 auto large = filter (nums , []( int x) { return x > 5; });
20

21 auto print = []( const std :: vector <int >& v) {


22 for (int x : v) std :: cout << x << " ";
23 std :: cout << "\n";
24 };
25
26 print ( positives ); // 1 4 7 9
27 print ( negatives ); // -5 -3 -2
28 print ( large ); // 7 9
29
30 return 0;
31 }

Listing 23: Generic filter helper

9 Reduce — std::accumulate / std::reduce


The reduce (also called fold) operation combines all elements of a collection into a
single value using a binary function and an initial value.

9.1 Using std::accumulate


std::accumulate is found in <numeric> and performs a left fold:

( ( ) )
result = f f . . . f (f (init, x0 ), x1 ), . . . , xn−1 (1)

17
C++ Tutorial Series Functional Programming in C++

1 # include <iostream >


2 # include <vector >
3 # include <numeric >
4 # include <string >
5 # include <functional >
6
7 int main () {
8 std :: vector <int > nums = {1, 2, 3, 4, 5};
9
10 // �� Sum �������������������������������������������������������
11 int sum = std :: accumulate (nums. begin () , [Link] () , 0);
12 std :: cout << "sum = " << sum << "\n"; // 15
13
14 // �� Product ���������������������������������������������������
15 int product = std :: accumulate (nums. begin () , [Link] () , 1,
16 []( int acc , int x) { return acc * x;
});
17 std :: cout << " product = " << product << "\n"; // 120
18
19 // �� Maximum ���������������������������������������������������
20 int maxVal = std :: accumulate (nums. begin () , [Link] () ,
21 nums [0] ,
22 []( int a, int b) { return (a > b) ? a
: b; });
23 std :: cout << "max = " << maxVal << "\n"; // 5
24
25 // �� String join �����������������������������������������������
26 std :: vector <std :: string > words = {"one", "two", " three "};
27 std :: string joined = std :: accumulate (
28 std :: next( words . begin ()), words .end () ,
29 words [0] , // initial value is first word
30 []( const std :: string & acc , const std :: string & w) {
31 return acc + ", " + w;
32 });
33 std :: cout << " joined = " << joined << "\n"; // one , two , three
34
35 return 0;
36 }

Listing 24: std::accumulate — the classic reduce

9.2 Computing Statistics with Reduce


1 # include <iostream >
2 # include <vector >
3 # include <numeric >
4 # include <cmath >
5
6 int main () {
7 std :: vector <double > data = {2.0 , 4.0 , 4.0 , 4.0 , 5.0 , 5.0 , 7.0 ,
9.0};
8 int n = [Link] ();
9
10 // Mean
11 double sum = std :: accumulate (data. begin () , [Link] () , 0.0);
12 double mean = sum / n;
13 std :: cout << "Mean = " << mean << "\n"; // 5.0

18
C++ Tutorial Series Functional Programming in C++

14
15 // Variance = mean of squared deviations
16 double variance = std :: accumulate (data. begin () , [Link] () , 0.0 ,
17 [mean ]( double acc , double x) {
18 return acc + (x - mean) * (x - mean);
19 }) / n;
20 std :: cout << " Variance = " << variance << "\n"; // 4.0
21 std :: cout << "Std Dev = " << std :: sqrt( variance ) << "\n"; // 2.0
22
23 return 0;
24 }

Listing 25: Computing mean and variance using accumulate

9.3 std::reduce (C++17) — Parallel-Friendly


C++17 introduced std::reduce (also in <numeric>). Unlike accumulate, it does not
guarantee left-to-right evaluation order, enabling parallel execution:
1 # include <iostream >
2 # include <vector >
3 # include <numeric >
4 # include <execution > // for parallel policies ( requires linking TBB/
OpenMP )
5
6 int main () {
7 std :: vector <long long > v(1 '000 '000);
8 std :: iota(v. begin () , [Link] () , 1LL); // fill with 1 .. 1000000
9
10 // Sequential reduce
11 long long seqSum = std :: reduce (v. begin () , [Link] () , 0LL);
12 std :: cout << " Sequential sum = " << seqSum << "\n";
13

14 // Parallel reduce (C++17 , requires appropriate execution policy


support )
15 // long long parSum = std :: reduce (
16 // std :: execution ::par , v. begin () , [Link] () , 0LL);
17
18 // Custom binary operation
19 long long product3 = std :: reduce (
20 v. begin () , v. begin () + 5, // first 5 elements : 1*2*3*4*5
21 1LL ,
22 []( long long a, long long b) { return a * b; });
23 std :: cout << "5! via reduce = " << product3 << "\n"; // 120
24

25 return 0;
26 }

Listing 26: std::reduce — C++17 parallel-friendly reduction

9.4 Building a Reusable reduce Helper


1 # include <iostream >
2 # include <vector >
3 # include <numeric >
4

19
C++ Tutorial Series Functional Programming in C++

5 template <typename T, typename BinaryOp >


6 T reduce ( const std :: vector <T >& v, T init , BinaryOp op) {
7 return std :: accumulate (v. begin () , [Link] () , init , op);
8 }
9
10 int main () {
11 std :: vector <int > nums = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
12

13 int sum = reduce (nums , 0, []( int a, int b) { return a + b; });


14 int prod = reduce (nums , 1, []( int a, int b) { return a * b; });
15 int maxV = reduce (nums , nums [0] , []( int a, int b) { return a > b ?
a : b; });
16
17 std :: cout << "Sum = " << sum << "\n"; // 55
18 std :: cout << " Product = " << prod << "\n"; // 3628800
19 std :: cout << "Max = " << maxV << "\n"; // 10
20
21 return 0;
22 }

Listing 27: Generic reduce helper

10 Composing Map, Filter, and Reduce


The real power of functional programming emerges when these three patterns are
chained together into a pipeline:

( ( ) )
reduce filter map(input, f ), p , g

10.1 Complete Pipeline Example


1 # include <iostream >
2 # include <vector >
3 # include <algorithm >
4 # include <numeric >
5 # include <iterator >
6
7 // Reusable helpers
8 template <typename T, typename F>
9 auto mapV( const std :: vector <T >& v, F f) {
10 using R = decltype (f(v[0]));
11 std :: vector <R> out([Link] ());
12 std :: transform (v. begin () , [Link] () , out. begin () , f);
13 return out;
14 }
15

16 template <typename T, typename P>


17 std :: vector <T> filterV ( const std :: vector <T >& v, P p) {
18 std :: vector <T> out;
19 std :: copy_if (v. begin () , [Link] () , std :: back_inserter (out), p);
20 return out;
21 }
22
23 template <typename T, typename B>

20
C++ Tutorial Series Functional Programming in C++

24 T reduceV ( const std :: vector <T >& v, T init , B op) {


25 return std :: accumulate (v. begin () , [Link] () , init , op);
26 }
27
28 int main () {
29 // Problem : given student scores , find the average of all
30 // passing scores (>= 50) after a 10% bonus .
31

32 std :: vector <int > rawScores = {30 , 45, 55, 70, 20, 80, 95, 48, 60};
33
34 // Step 1: MAP — apply 10% bonus
35 auto boosted = mapV(rawScores ,
36 []( int score ) { return static_cast <int >( score * 1.10) ; });
37

38 // Step 2: FILTER — keep only passing scores (>= 50)


39 auto passing = filterV (boosted ,
40 []( int score ) { return score >= 50; });
41
42 // Step 3: REDUCE — compute the sum , then calculate average
43 int total = reduceV (passing , 0,
44 []( int acc , int s) { return acc + s; });
45
46 double average = passing . empty () ? 0.0
47 : static_cast <double >( total ) /
passing .size ();
48
49 // Print intermediate results
50 std :: cout << "Raw scores : ";
51 for (int s : rawScores ) std :: cout << s << " ";
52 std :: cout << "\ nAfter bonus : ";
53 for (int s : boosted ) std :: cout << s << " ";
54 std :: cout << "\ nPassing ( >=50): ";
55 for (int s : passing ) std :: cout << s << " ";
56 std :: cout << "\ nCount : " << passing .size ()
57 << " Sum: " << total
58 << " Average : " << average << "\n";
59

60 return 0;
61 }

Listing 28: Chaining map, filter, and reduce

Sample Output:
Raw scores : 30 45 55 70 20 80 95 48 60
After bonus : 33 49 60 77 22 88 104 52 66
Passing ( >=50): 60 77 88 104 52 66
Count: 6 Sum: 447 Average : 74.5

11 Advanced Topics
11.1 Partial Application with std::bind
std::bind (from <functional>) allows you to fix some arguments of a function and
produce a new function:

21
C++ Tutorial Series Functional Programming in C++

1 # include <iostream >


2 # include <functional >
3 # include <vector >
4 # include <algorithm >
5
6 int add(int a, int b) { return a + b; }
7 bool greaterThan (int threshold , int value ) { return value > threshold ;
}
8
9 int main () {
10 using namespace std :: placeholders ;
11
12 // Partial application : fix first argument of add
13 auto add5 = std :: bind(add , 5, _1); // _1 is the remaining arg
14 auto add10 = std :: bind(add , 10, _1);
15
16 std :: cout << add5 (3) << "\n"; // 8
17 std :: cout << add10 (3) << "\n"; // 13
18

19 // Use in algorithm
20 std :: vector <int > nums = {1, 3, 5, 7, 9, 11, 13};
21 auto greaterThan7 = std :: bind( greaterThan , 7, _1);
22
23 auto count = std :: count_if (nums. begin () , [Link] () , greaterThan7 );
24 std :: cout << " Count > 7: " << count << "\n"; // 3
25
26 return 0;
27 }

Listing 29: Partial application using std::bind

Tip
In modern C++, lambdas are generally preferred over std::bind because they
are more readable, easier to debug, and often perform better. However std::bind
remains useful for interfacing with older APIs.

11.2 Function Composition


1 # include <iostream >
2 # include <functional >
3
4 // Compose two functions : compose (f, g)(x) = f(g(x))
5 template <typename F, typename G>
6 auto compose (F f, G g) {
7 return [f, g]( auto x) { return f(g(x)); };
8 }
9
10 // Compose any number of functions using fold expression (C ++17)
11 template <typename F>
12 auto composeAll (F f) { return f; }
13
14 template <typename F, typename ... Fs >
15 auto composeAll (F f, Fs ... rest) {
16 return compose (f, composeAll (rest ...));
17 }
18

22
C++ Tutorial Series Functional Programming in C++

19 int main () {
20 auto addOne = []( int x) { return x + 1; };
21 auto doubleIt = []( int x) { return x * 2; };
22 auto square = []( int x) { return x * x; };
23
24 // double ( addOne (x))
25 auto f = compose (doubleIt , addOne );
26 std :: cout << " double ( addOne (3)) = " << f(3) << "\n"; // 8
27
28 // double ( addOne ( square (x)))
29 auto g = composeAll (doubleIt , addOne , square );
30 std :: cout << " double ( addOne ( square (3))) = " << g(3) << "\n"; // 20
31
32 return 0;
33 }

Listing 30: Function composition helper

12 Summary and Best Practices


12.1 Quick Reference Table
Concept C++ Mechanism Header
Function pointer ret (*ptr)(args) none
Functor struct with operator() none
Type-erased callable std::function<> <functional>
Anonymous function Lambda expression none
Partial application std::bind <functional>
Map std::transform <algorithm>
Filter (copy) std::copy_if <algorithm>
Filter (in-place) erase-remove idiom <algorithm>
Reduce (ordered) std::accumulate <numeric>
Reduce (parallel-ready) std::reduce <numeric>

12.2 Best Practices


1. Prefer lambdas over function pointers for local, one-shot callables. They are
more expressive and let the compiler inline the call.
2. Use templates instead of std::function when performance matters. std::function
incurs type-erasure overhead.
3. Avoid capturing by reference ([&]) in lambdas that outlive the enclosing
scope. Dangling references cause undefined behaviour.
4. Keep lambdas small. If a lambda body grows long, extract it into a named
function for readability and testability.
5. Use auto for lambda variables — each lambda has a unique, anonymous type;
auto is the only clean way to store it locally.
6. Chain map/filter/reduce to build readable data pipelines instead of nested loops.

23
C++ Tutorial Series Functional Programming in C++

7. Consider std::reduce with parallel execution policies for large datasets in


C++17 and later.
8. Mark lambdas mutable sparingly. Mutating local state often indicates you
should use a functor or a named class instead.
9. Use const& in lambda parameters for large objects to avoid unnecessary copies.
10. Prefer std::accumulate for portability and deterministic ordering; use std::reduce
when you need parallelism and your operator is associative and commutative.

24
C++ Tutorial Series Functional Programming in C++

Further Reading
• [Link] — [Link]
Comprehensive reference for all standard library algorithms and utilities.
• “Effective Modern C++” by Scott Meyers — Chapters on lambdas, std::function,
and std::bind.
• “C++ Templates: The Complete Guide” by Vandevoorde, Josuttis, and Gre-
gor — Advanced template techniques for functional programming patterns.
• ISO C++ Standard — [Link]
• Abseil C++ Tips — [Link]
Practical advice on modern C++ idioms including lambdas and functional patterns.

End of Tutorial — Functional Programming in C++ • C++11/14/17/20

25

You might also like