Doing It the Functional Way in C++

Sheik Arbaz
The Startup
Published in
6 min readAug 31, 2020

I’ve spent some time with C++ for doing Competitive Programming in my graduation days. But I never got a chance to use C++ in my office projects, I was coding in Functional programming(FP) languages. But after 2 years, I got an opportunity to work on C++. As a Functional Programming enthusiast who is very much habituated to the cool FP style which makes the life of developers easy, I started exploring ways to code in FP style in C++. I learnt C++ has got amazing features in the latest releases. In this blog, we will go through the basics of functional programming, and what parts of it are possible in C++.

Prerequisites:

  • A little experience in C++
  • A bit of experience with any Functional programming language or paradigm
  • A C++ compiler which supports C++11 standard

Let’s get started

Functional programming is a declarative type of programming style. Its sole focus is on “what to solve” in contrast to an imperative style where the main focus is on “how to solve“. It uses expressions instead of statements.

Functional Programming Key Concepts

  • Functions as first class objects
  • Pure functions

Functional Programming Rules

  • Immutable variables: In functional programming, you can’t modify a variable after it’s been initialized. You just can’t. You can create new variables but you can’t modify existing variables.
  • No side effects: A side effect is a state change in something other than the function that’s currently executing. Modifying a variable defined outside the function, printing out to the console, raising an exception, and reading data from a file are all examples of side effects.
  • No state: A function may have local variables containing temporary state internally, but the function cannot reference any member variables of the class or object the function belongs to. State encourages mutability leading to side effects. Functional Programming doesn’t want you to do that.

These are a few key concepts, rules of FP. Even if you do not follow all of these rules all the time, you can still benefit from the FP ideas in your applications. Anyways, C++ was never meant to be a strict or pure functional programming language. To be honest, functional programming is not the right tool for every problem(Yeah! It’s my opinion. It might change in future. Opinions do change with experience!). For now, let’s learn what problems FP is good at solving.

Functions as first class objects

In the functional programming world, functions are first-class objects. Functions are treated like any other variable. For example, a function can be passed as an argument to other functions and can be assigned as a value to a variable.

Functions as first class objects(pinterest.com)

In C++, functions are not first class objects. The closest we get is lambda expressions.

auto printText = [](std::string text) { std::cout << text << std::endl; };

Above code is creating a lambda printText, which takes a single parameter text , prints it and returns nothing. The [] brackets are used to specify the closure for the function. More information about lambdas can be found here.

Let’ see how we can do FP by looking at how we can apply different combinators like filter, map, reduce on C++ vector or any collection. Let’s take the below vector:

std::vector<std::string> messages = { "Hello Pal", "How are you?", "I'm still coding in C++" };

for_each

std::for_each(messages.begin(), messages.end(), printText);

First two parameters are starting and ending of the collection. Then the third parameter we pass is a unary lambda which operates on each element.

Let’s create a vector of custom student objects and apply the combinators on it.

class Student
{
public:
string _name;
int _score;
Student(string name, int score)
{
_name = name;
_score = score;
}
void incrementScore() {
_score += 1;
}
string name()
{
return _name;
}
int score()
{
return _score;
}
};
std::vector<Student> students = {Student("Alice", 85), Student("Bob", 62), Student("Charlie", 81), Student("Jack", 90), Student("Jimmy", 40), Student("Sherlock", 67),};

Assume, you’re the teacher and want to print their details. Then, you can use for_each to print their details.

auto printStudentDetails = [](Student student) { std::cout << student.name() << " " << student.score() << std::endl; };std::for_each(students.begin(), students.end(), printStudentDetails);

In the above code, we have used a printing lambda to print each student’s detail.

map

In C++, the equivalent for the functional map is transform. Assume, You gave a one mark question with insufficient/invalid data and want to add one mark to every student. Then you can use transform to update every student’s score.

auto addOne = [](Student student) { student.incrementScore(); return student; };
std::transform(students.begin(), students.end(), students.begin(), addOne);

You’ll have to provide the begin, end pointers of the input collection, the begin pointer of the output collection and the operation. You can even perform transform on two collections at once. You can check it out, it’s out of the scope of this blog.

filter

In C++, the functional filter has many equivalents based on the use-case. If you just want to get a copy of elements with specific properties, you can use copy_if. Anyhow, there are other functions like remove_if, find_if, etc too. And each such function has a “not” alternate as well like copy_if_not, find_if_not, etc.

auto aboveEighty = [](Student student) { return student.score() > 80; };
vector<Student> topStudents = {};
auto it = std::copy_if(students.begin(), students.end(), back_inserter(topStudents), aboveEighty);
std::for_each(topStudents.begin(), topStudents.end(), printStudentDetails);

The syntax for copy_if is: copy_if(Iterator inputBegin, Iterator inputEnd, Iterator outputBegin, predicate). You’ll have to provide the begin, end pointers of the input collection, the begin pointer of the output collection and the operation. I’ve used back_inserter_iterator instead of outputBegin which enables me to push the elements into topStudents vector without needing to initialize the vector.

Reduce or Fold

The functional reduce equivalent in C++ is accumulate. Assume you’ve a vector with numbers and you want to sum them all.

vector<int> numbers{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
std::cout << std::accumulate(numbers.begin(), numbers.end(), 0,
[](int first, int second) { return first + second; });

You’ll have to provide the begin, end pointers of the input collection, the initial value of the accumulator and a binary lambda. Accumulator can also be applied to our custom students vector. Below code calculates the average score of the classroom.

auto binaryOp = [](int score, Student student) { return score + student.score(); };
std::cout << std::accumulate(students.begin(), students.end(), 0, binaryOp)/students.size();

Pure Functions

A function is a pure function if:

  • The execution of the function has no side effects.
  • The return value of the function depends only on the input parameters passed to the function.
int sum(int a, int b){
return a+b;
}

Above created function is a pure function. Many in-built C++ functions are pure like min, max, strlen, etc. You can write functions which accept all const parameters and don’t change the instance variables. In GCC, you can even mark functions as pure using the “pure” attribute which enables better optimization. If a function is known as pure to the compiler then Loop optimization and subexpression elimination can be applied to it. But C++ allows side-effects, mutability as well by default.

public class NonPureFunctionAndMutatingParametersExample{
private int total= 0;

public int add(int nextValue) {
this.total+= nextValue;
return this.total;
}
public void incrementScore(Student &student){
student.score += 1;
}
}

In the above code:

  • add method is changing the state
  • incrementScore method is changing the function parameters

We should avoid doing the above things unless necessary. Yes, there are cases where side effects, mutability would benefit.

The rule Immutable variables is a good practice to follow. Once you create a variable and set its value, you can have full confidence knowing that the value of that variable will never change but sometimes it is not suitable. If you’re building an application which has to run in the end-users low configuration machines, then you’ll have limited memory, time. In such cases, it’s suggested to accept the references/pointers of the parameters, mutate them to save memory and copying time.

The rule No side effects is very helpful. It gives confidence that this function won’t affect the outside world and it’s safe to call it anywhere. But it makes it hard in some scenarios e.g. writing to a database (that is a side effect).

There are some other concepts, rules like Higher order functions, recursion over loop, etc which can be applied whenever needed.

Conclusion

As I said earlier, Functional Programming is an amazing paradigm but there are scenarios where not abiding a few rules of it gives optimal solutions.

C++ has changed a lot in the last few years. The new constructs make it an amazing tool to build any kind of applications. It’s no more just C with Classes. Happy Coding!!!

--

--