{"slug": "function-composition-from-c-17-to-c-23", "title": "Function Composition from C++17 to C++23", "summary": "C++23 code capsules trace the evolution of function composition from C++17 to C++23, building a reusable Composer class that grows cleaner with each standard. The journey highlights how C++ adopted functional programming concepts like right folds, moving from a function-type parameterization in C++17 to a value-type parameterization in C++20, with further improvements expected in C++23.", "body_md": "*C++23 Code Capsules*\n\nMathematicians and programmers alike have always known that functions\nare things you can do things *with*, not just things you\n*call*. One of the most natural operations on functions is\ncomposition: given f and g, form the new function (f ∘ g) where (f ∘\ng)(x) = f(g(x)). Chain enough of these together and you have a pipeline\n— a sequence of transformations applied one after another.\n\nFunctional programmers have known this for decades. In Standard ML, folding a list of functions over an initial value is idiomatic and concise:\n\n```\n(* ML: apply a list of functions right-to-left *)\nfun compose fs x = foldr (fn (f, acc) => f acc) x fs\n```\n\n`foldr`\n\nprocesses the list from the right, threading an\naccumulator through each function in turn. The result is function\ncomposition expressed as a fold — \\(f_1(f_2(...f_n(x)...))\\) — and the\ncombining function takes `(element, accumulator)`\n\nin\nright-to-left order.\n\nC++, being based on a procedural language that put performance first,\ntook a longer road to arrive at the same idea. But arrive it did. This\ncapsule traces that journey across three standards, building a reusable\n`Composer`\n\nclass that grows cleaner at each step.\n\nHere is a first cut, written in C++17:\n\n```\n// Makes the function type generic\n#include <algorithm>\n#include <functional>\n#include <iostream>\n#include <numeric>\n#include <vector>\nusing namespace std;\n\ntemplate<typename Fun>\nclass Composer {\n    vector<Fun>& funs;\npublic:\n    Composer(vector<Fun>& fs) : funs(fs) {}\n    using T = typename Fun::result_type;\n    T operator()(T x) const {\n        auto apply = [](T sofar, Fun f){ return f(sofar); };\n        return accumulate(rbegin(funs), rend(funs), x, apply);\n    }\n};\n\nstruct g {\n    double operator()(double x) { return x * x; }\n};\n\nint main() {\n    auto f = [](double x){ return x / 2.0; };\n    using Fun = function<double(double)>;\n    vector<Fun> funs{f, g(), [](double x){ return x + 1.0; }};\n    Composer<Fun> comp(funs);\n    cout << comp(2.0) << \"\\n\";          // 4.5\n\n    using Fun2 = function<string(const string&)>;\n    vector<Fun2> funs2{\n        [](const string& s){ return s + \"s\"; },\n        [](const string& s){ return s + \"'\"; }\n    };\n    Composer<Fun2> comp2(funs2);\n    cout << comp2(\"Vernor\") << \"\\n\";    // Vernor's\n}\n```\n\nThe core of the class is this line:\n\n```\nreturn accumulate(rbegin(funs), rend(funs), x, apply);\n```\n\nWalking the vector in reverse and folding left is equivalent to\nfolding right — it applies the last function first, then the\nsecond-to-last, and so on. This is exactly ML’s `foldr`\n\nin\ndisguise, expressed through `std::accumulate`\n\nand reversed\niterators. It works, but the disguise is unfortunate: the intent is a\nright fold, but it isn’t crystal clear in the code.\n\nThere is also a more practical problem. `Composer`\n\nis\nparameterized on the *function type* `Fun`\n\n, and it\nextracts the value type via `Fun::result_type`\n\n. That member\nonly exists on `std::function`\n\n, not on raw lambdas or\nfunction objects. The\n`using Fun = function<double(double)>`\n\nin\n`main`\n\nis not incidental — it is required. The class forces\nits clients to wrap their callables.\n\nC++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.\n\nFlipping the template parameter from `Fun`\n\nto\n`T`\n\ngives us this:\n\n```\ntemplate<typename T>\nclass Composer {\n    vector<function<T(T)>> funs;\npublic:\n    Composer(vector<function<T(T)>> fs) : funs(move(fs)) {}\n\n    T operator()(T x) const {\n        auto apply = [](T acc, auto f){ return f(acc); };\n        return accumulate(rbegin(funs), rend(funs), x, apply);\n    }\n};\n```\n\nNow `main`\n\nreads naturally:\n\n```\nComposer<double> comp({ ... });\nComposer<string> comp2({ ... });\n```\n\n`Composer<double>`\n\nmeans what it says: a composition\nof functions on `double`\n\n. The `std::function`\n\nwrapping still happens, but it is now an internal detail of the class,\nnot something the caller needs to explicitly name. C++20 concepts could\nfurther constrain `T`\n\n(to require copyability, say, or to\nexpress the same input as output type requirement), but for a capsule of\nthis size the improvement in readability already tells the story.\n\nC++23 brings two things that complete the picture:\n`std::ranges::fold_right`\n\n, and a usable implementation of\n*modules*. (I’ll admit that this problem is small enough to not\nrequire a module; in fact `Composer`\n\ncould be a part of a\nlarger module, but indulge me here. :-)\n\n`fold_right`\n\nreplaces the\n`accumulate(rbegin, rend, ...)`\n\nidiom with something that\nnames its intent directly:\n\n```\nT operator()(T x) const {\n    return std::ranges::fold_right(funs, x, [](auto f, auto acc){ return f(acc); });\n}\n```\n\nNotice the argument order in the lambda:\n`(element, accumulator)`\n\n. This is the same order as ML’s\n`foldr`\n\ncombining function —\n`fn (f, acc) => f acc`\n\n. That is not a coincidence. C++ has\nabsorbed the idea, and the interface reflects it.\n\nThe full module interface file:\n\n``` python\n// composer.cppm\nexport module composer;\n\nimport std;\n\nexport template<typename T>\nclass Composer {\n    std::vector<std::function<T(T)>> funs;\npublic:\n    Composer(std::vector<std::function<T(T)>> fs) : funs(std::move(fs)) {}\n\n    T operator()(T x) const {\n        return std::ranges::fold_right(funs, x, [](auto f, auto acc){ return f(acc); });\n    }\n};\n```\n\nAnd the test driver that consumes it:\n\n``` python\n// compose23.cpp\nimport composer;\nimport std;\n\nint main() {\n    Composer<double> comp({\n        [](double x){ return x / 2.0; },\n        [](double x){ return x * x; },\n        [](double x){ return x + 1.0; }\n    });\n    std::cout << comp(2.0) << \"\\n\";         // 4.5\n\n    Composer<std::string> comp2({\n        [](std::string s){ return s + \"s\"; },\n        [](std::string s){ return s + \"'\"; }\n    });\n    std::cout << comp2(\"Vernor\") << \"\\n\";   // Vernor's\n}\n```\n\n`Composer`\n\nis a natural fit for a module: it is\nself-contained, has no platform dependencies, and exports exactly one\nthing. The `import std;`\n\nin the test driver replaces a\nhalf-dozen headers. The result is as clean as the class deserves.\n\nWe started with a right fold expressed as `accumulate`\n\nover reversed iterators — a correct but oblique encoding of an idea that\nML stated directly in the 1970s. Over three standards, C++ acquired the\nvocabulary to say the same thing clearly: a value-typed interface, a\nnamed `fold_right`\n\n, a module boundary that packages the\nabstraction for reuse.\n\nThis is the direction modern C++ has been moving for some time.\nRanges, folds, `std::function`\n\n, concepts, modules — these are\nnot disconnected features. They are the pieces of a language that is\ngradually adopting powerful ideas in its own idiom, an idiom that has\nhad a sometimes-hard-to-follow syntactic path, but has delivered\nperformance and code compatibility without peer.\n\nWhat goes around comes around.", "url": "https://wpnews.pro/news/function-composition-from-c-17-to-c-23", "canonical_source": "https://freshsources.com/code-capsules/composing-functions/", "published_at": "2026-06-17 12:29:20+00:00", "updated_at": "2026-06-17 12:53:02.327431+00:00", "lang": "en", "topics": ["developer-tools"], "entities": ["C++17", "C++20", "C++23", "Standard ML"], "alternates": {"html": "https://wpnews.pro/news/function-composition-from-c-17-to-c-23", "markdown": "https://wpnews.pro/news/function-composition-from-c-17-to-c-23.md", "text": "https://wpnews.pro/news/function-composition-from-c-17-to-c-23.txt", "jsonld": "https://wpnews.pro/news/function-composition-from-c-17-to-c-23.jsonld"}}