Is C++ Ready for Functional Programming? – Standard Library

man standing inside library while reading book

Time to take a closer look to the standard library from the functional programming point of view.

In the first installment of this series, I stated that using a library to implement functional programming structures would not be an ideal solution, but as C language pioneered in the 70s, part of the language finds its proper location in a library.

C++ standard library has grown disorderly oversize during the years, so let’s have a look at what kind of support is available for those that want to use C++ with the functional paradigm.

If the language does not do much for helping the functional programmer, the standard library, at least up to C++17, tends to be a bit hostile.

First, there’s no support for immutable containers. Whereas Scala has two distinct hierarchies for mutable and immutable containers, STL just provides the mutable ones. Although it is true that you can declare them const, they need to be mutable in order to grow or shrink.

Take the immutable.Set[T] container in Scala. If you want to add an element, you use a method that given the existing container and the new element, creates a new Set made from the union of the previous one and the item you wanted to add.

Using a C++ mutable container, you could write something like :

std::set<T> add( std::set<T> const& original, T const& item )
{
    std::set<T> newSet( original );
    newSet.insert( item );
    return newSet;
}

But then you can’t make newSet const because you need to move it out via the return statement. And if isn’t const, there is nothing preventing a user to modify the data structure. So, yes it could work, as long as the programmer is willing to keep the code functional and stick to the conventions… good luck.

In language with functional programming paradigm built-in usually you find some monadic operations. Notably the map and flatMap methods are available for every container.

In C++ the closest thing is the std::transform algorithm that takes a begin/end iterator pair for the source range and then a destination iterator as the destination. In other words the Scala code:

val a = Vector( "a", "bb", "ccc" )
val b = a.map( _.length )

Can be roughly translated in C++ with:

#include <vector>
#include <string>
#include <algorithm>
​
auto a = std::vector<std::string>{ "a", "bb", "ccc" };
auto b = std::vector<size_t>{ a.size() };
std::transform( a.begin(), a.end(), b.begin(), [](auto& s){return s.size();} );
}

Note that std::transform, differently from map, cannot be chained with other transformation e.g. filter and flatMap. This prevents for sure the chain of death but also prevents many valid and expressive constructs.

Just to be clear, it is not a matter of keystroke count, but a matter of better readability achieved by conciseness.

Special kinds of functional containers are Option, Either and Future. In C++ there are equivalents for all three.

They are std::optional (since C++17), std::variant (since C++17) and std::future (since C++11), respectivley.

Regretfully C++ fails in recognizing that these classes a) are all containers and thus provides no begin/end and b) their functional nature so that transformational methods are not provided.

Having no chaining capability, the imperative approach is the only available, and that’s just a pity because it could have been a straightforward addition.

It is particularly annoying with std::future, since there is no way to program the transformation of the future data asynchronously.

To better clarify, in Scala you can write something like:

def fn( f: Future[Int]) : Future[String] = {
    f.map( _.toString )
}

To define a transformation of the future data as soon as it will be available.

In the example, when f is completed, its value will be mapped to a string, so turning it effectively into a future of String.

An array of futures can be turned, in a similar way, into a future of arrays giving an easy, safe, and natural way to combine futures together. And this can’t be done in C++ with the tools provided by std::future alone.

std::variant is possibly the most different from its functional counterpart. Either has just two alternatives, while std::variant has any number you need. Moreover Either is a bit biased – assigning different semantic to its alternatives (Left – wrong, and Right… right) and this makes it much more useful.

A notable mention is the so-called range library available since C++20 (and therefore possibly not that widespread yet). This part of the standard library is clearly inspired by functional chaining. One example of the range library follows:

auto const ints = {0,1,2,3,4,5};
 
    // "pipe" syntax of composing the views:
auto x = ints |
    std::views::filter([](int i) { return 0 == i % 2; }) |
    std::views::transform([](int i) { return i * i; }));

x is a view on the original container, meaning that only the used values will be computed (this is called lazy evaluation). On the other hand, the clumsy lambda syntax and the language limitations don’t help. By language limitations, I mean that you have to assign the integer list to a variable (even with auto declaration), because you cannot start the range chain with the initialization list –

{0,1,2,3,4,5} | std::views::filter( ...  // doesn't work
std::array{0,1,2,3,4,5} | std::views::filter( ... // ok

So, std::ranges is for sure a step in the right functional direction, but it keeps being limited by language limitation and by using a dictionary that is not the functional programming dictionary –

C++FP
std::transformmap
std::copy_iffilter
std::iotato
std::taketake
std::dropdrop
std::joinflatten
std::accumulatefold

Now this table shows another problem, not a show stopper, but still a problem. If you are alone in the universe you can call something whatever you want. No problem. But if a community exists with a precise and coded dictionary, you can’t claim to support such a community and speak another language. If you want to get a funny look from a functional programmer try calling transform what she has been taught to call map.

Despite pretending functional paradigm support, C++ standard library shows no sign of such a thing. Having functions that accept functions as predicates, it is really not more functional oriented than the C library qsort that accepts a comparison callback.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.