# Function Composition from C++17 to C++23

> Source: <https://freshsources.com/code-capsules/composing-functions/>
> Published: 2026-06-17 12:29:20+00:00

*C++23 Code Capsules*

Mathematicians and programmers alike have always known that functions
are things you can do things *with*, not just things you
*call*. One of the most natural operations on functions is
composition: given f and g, form the new function (f ∘ g) where (f ∘
g)(x) = f(g(x)). Chain enough of these together and you have a pipeline
— a sequence of transformations applied one after another.

Functional programmers have known this for decades. In Standard ML, folding a list of functions over an initial value is idiomatic and concise:

```
(* ML: apply a list of functions right-to-left *)
fun compose fs x = foldr (fn (f, acc) => f acc) x fs
```

`foldr`

processes the list from the right, threading an
accumulator through each function in turn. The result is function
composition expressed as a fold — \(f_1(f_2(...f_n(x)...))\) — and the
combining function takes `(element, accumulator)`

in
right-to-left order.

C++, being based on a procedural language that put performance first,
took a longer road to arrive at the same idea. But arrive it did. This
capsule traces that journey across three standards, building a reusable
`Composer`

class that grows cleaner at each step.

Here is a first cut, written in C++17:

```
// Makes the function type generic
#include <algorithm>
#include <functional>
#include <iostream>
#include <numeric>
#include <vector>
using namespace std;

template<typename Fun>
class Composer {
    vector<Fun>& funs;
public:
    Composer(vector<Fun>& fs) : funs(fs) {}
    using T = typename Fun::result_type;
    T operator()(T x) const {
        auto apply = [](T sofar, Fun f){ return f(sofar); };
        return accumulate(rbegin(funs), rend(funs), x, apply);
    }
};

struct g {
    double operator()(double x) { return x * x; }
};

int main() {
    auto f = [](double x){ return x / 2.0; };
    using Fun = function<double(double)>;
    vector<Fun> funs{f, g(), [](double x){ return x + 1.0; }};
    Composer<Fun> comp(funs);
    cout << comp(2.0) << "\n";          // 4.5

    using Fun2 = function<string(const string&)>;
    vector<Fun2> funs2{
        [](const string& s){ return s + "s"; },
        [](const string& s){ return s + "'"; }
    };
    Composer<Fun2> comp2(funs2);
    cout << comp2("Vernor") << "\n";    // Vernor's
}
```

The core of the class is this line:

```
return accumulate(rbegin(funs), rend(funs), x, apply);
```

Walking the vector in reverse and folding left is equivalent to
folding right — it applies the last function first, then the
second-to-last, and so on. This is exactly ML’s `foldr`

in
disguise, expressed through `std::accumulate`

and reversed
iterators. It works, but the disguise is unfortunate: the intent is a
right fold, but it isn’t crystal clear in the code.

There is also a more practical problem. `Composer`

is
parameterized on the *function type* `Fun`

, and it
extracts the value type via `Fun::result_type`

. That member
only exists on `std::function`

, not on raw lambdas or
function objects. The
`using Fun = function<double(double)>`

in
`main`

is not incidental — it is required. The class forces
its clients to wrap their callables.

C++20 does not change the algorithm, but it invites a rethinking of the interface. The right abstraction for single-valued functions is not “a composer parameterized on a function type” — it is “a composer parameterized on a value type.” The functions are an implementation detail; what matters to the caller is the type they are transforming.

Flipping the template parameter from `Fun`

to
`T`

gives us this:

```
template<typename T>
class Composer {
    vector<function<T(T)>> funs;
public:
    Composer(vector<function<T(T)>> fs) : funs(move(fs)) {}

    T operator()(T x) const {
        auto apply = [](T acc, auto f){ return f(acc); };
        return accumulate(rbegin(funs), rend(funs), x, apply);
    }
};
```

Now `main`

reads naturally:

```
Composer<double> comp({ ... });
Composer<string> comp2({ ... });
```

`Composer<double>`

means what it says: a composition
of functions on `double`

. The `std::function`

wrapping still happens, but it is now an internal detail of the class,
not something the caller needs to explicitly name. C++20 concepts could
further constrain `T`

(to require copyability, say, or to
express the same input as output type requirement), but for a capsule of
this size the improvement in readability already tells the story.

C++23 brings two things that complete the picture:
`std::ranges::fold_right`

, and a usable implementation of
*modules*. (I’ll admit that this problem is small enough to not
require a module; in fact `Composer`

could be a part of a
larger module, but indulge me here. :-)

`fold_right`

replaces the
`accumulate(rbegin, rend, ...)`

idiom with something that
names its intent directly:

```
T operator()(T x) const {
    return std::ranges::fold_right(funs, x, [](auto f, auto acc){ return f(acc); });
}
```

Notice the argument order in the lambda:
`(element, accumulator)`

. This is the same order as ML’s
`foldr`

combining function —
`fn (f, acc) => f acc`

. That is not a coincidence. C++ has
absorbed the idea, and the interface reflects it.

The full module interface file:

``` python
// composer.cppm
export module composer;

import std;

export template<typename T>
class Composer {
    std::vector<std::function<T(T)>> funs;
public:
    Composer(std::vector<std::function<T(T)>> fs) : funs(std::move(fs)) {}

    T operator()(T x) const {
        return std::ranges::fold_right(funs, x, [](auto f, auto acc){ return f(acc); });
    }
};
```

And the test driver that consumes it:

``` python
// compose23.cpp
import composer;
import std;

int main() {
    Composer<double> comp({
        [](double x){ return x / 2.0; },
        [](double x){ return x * x; },
        [](double x){ return x + 1.0; }
    });
    std::cout << comp(2.0) << "\n";         // 4.5

    Composer<std::string> comp2({
        [](std::string s){ return s + "s"; },
        [](std::string s){ return s + "'"; }
    });
    std::cout << comp2("Vernor") << "\n";   // Vernor's
}
```

`Composer`

is a natural fit for a module: it is
self-contained, has no platform dependencies, and exports exactly one
thing. The `import std;`

in the test driver replaces a
half-dozen headers. The result is as clean as the class deserves.

We started with a right fold expressed as `accumulate`

over reversed iterators — a correct but oblique encoding of an idea that
ML stated directly in the 1970s. Over three standards, C++ acquired the
vocabulary to say the same thing clearly: a value-typed interface, a
named `fold_right`

, a module boundary that packages the
abstraction for reuse.

This is the direction modern C++ has been moving for some time.
Ranges, folds, `std::function`

, concepts, modules — these are
not disconnected features. They are the pieces of a language that is
gradually adopting powerful ideas in its own idiom, an idiom that has
had a sometimes-hard-to-follow syntactic path, but has delivered
performance and code compatibility without peer.

What goes around comes around.
