Upgrade to Pro — share decks privately, control downloads, hide ads and more …

TechTown - Creating Composable Callables in Con...

TechTown - Creating Composable Callables in Contemporary C++

C++ took a huge step in the direction of composability when Ranges were introduced in C++20. C++23 improved it, and C++26 looks to improve it even more. However, function objects, sometimes referred to as Niebloids, after Eric Niebler who pioneered their use in the Ranges library, are still few and rarely composable. With function objects we can send functions, and even complete overload sets, as arguments to other functions, return them, and combine them.

I will show you compositions I want, how to write them and with gradual scope creep end up with a very powerful set of utilities that are easy to use and require surprisingly little code when using recent additions to the C++ language.

Avatar for Björn Fahller

Björn Fahller

September 23, 2026

More Decks by Björn Fahller

Other Decks in Programming

Transcript

  1. Creating Composable Callables in Contemporary C++ or How I nerd

    sniped myself with a lightning talk https://www.youtube.com/watch?v=XKVyoWvPCCw Björn Fahller
  2. The goal auto i = find_if(values, &S::name | equal_to("foo")); sort(values,

    transform_args(&S::num, less_than)); std::vector<int> producer(); sort(producer(), greater_than);
  3. The goal auto i = find_if(values, &S::name | equal_to("foo")); sort(values,

    transform_args(&S::num, less_than)); std::vector<int> producer(); sort(producer(), greater_than); find_if(values, &S::name | equal_to("foo"));
  4. A small example bool all_are_small(std::span<const int> numbers) { return std::ranges::all_of(numbers,

    [](int x){return x < 10;}); } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 10/186
  5. A small example bool all_are_small(std::span<const int> numbers) { return std::ranges::all_of(numbers,

    [](int x){return x < 10;}); } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 11/186
  6. A small example Let’s make a function for this comparison

    bool all_are_small(std::span<const int> numbers) { return std::ranges::all_of(numbers, [](int x){return x < 10;}); } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 12/186
  7. A small example constexpr auto less_than(auto rh) { return [rh](auto

    lh){return lh < rh;}; } bool all_are_small(std::span<const int> numbers) { return std::ranges::all_of(numbers, [](int x){return x < 10;}); } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 13/186
  8. A small example constexpr auto less_than(auto rh) { return [rh](auto

    lh){return lh < rh;}; } bool all_are_small(std::span<const int> numbers) { return std::ranges::all_of(numbers, [](int x){return x < 10;}); } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 14/186
  9. A small example constexpr auto less_than(auto rh) { return [rh](auto

    lh){return lh < rh;}; } Return a lambda which binds the argument used on the right hand side bool all_are_small(std::span<const int> numbers) { return std::ranges::all_of(numbers, [](int x){return x < 10;}); } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 15/186
  10. A small example constexpr auto less_than(auto rh) { return [rh](auto

    lh){return lh < rh;}; } bool all_are_small(std::span<const int> numbers) { return std::ranges::all_of(numbers, less_than(10)); } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 16/186
  11. A small example constexpr auto less_than(auto rh) { return [rh](auto

    lh){return lh < rh;}; } bool all_are_small(std::span<const int> numbers) { return std::ranges::all_of(numbers, less_than(10)); } https://godbolt.org/z/415qhrbPa Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 17/186
  12. A first generalization constexpr auto less_than(auto rh) { return [rh](auto

    lh){return lh < rh;}; } This has a name in the numbers) standard library. It is std::less<> bool all_are_small(std::span<const int> { return std::ranges::all_of(numbers, less_than(10)); } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 18/186
  13. A first generalization constexpr auto less_than(auto rh) { return [rh](auto

    lh){return lh < rh;}; [f,bound](auto arg) { } return f(arg, bound); }; bool all_are_small(std::span<const int> numbers) { return std::ranges::all_of(numbers, less_than(10)); } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 19/186
  14. A first generalization Given a function and a bound argument

    constexpr auto less_than(auto rh) { return [rh](auto lh){return lh < rh;}; [f,bound](auto arg) { } return f(arg, bound); }; bool all_are_small(std::span<const int> numbers) { return std::ranges::all_of(numbers, less_than(10)); } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 20/186
  15. A first generalization constexpr auto less_than(auto rh) { return [rh](auto

    lh){return lh < rh;}; [f,bound](auto arg) { } return f(arg, bound); }; bool all_are_small(std::span<const int> numbers) Call the function with the { left hand side argument return std::ranges::all_of(numbers, and the bound right hand less_than(10)); side argument } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 21/186
  16. A first generalization constexpr auto less_than(auto rh) { [f](auto rh)

    { return [rh](auto lh){return lh < lh) rh;}; return [f,rh](auto { } return f(lh, rh); }; bool all_are_small(std::span<const int> numbers) }; { return std::ranges::all_of(numbers, less_than(10)); } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 22/186
  17. A first generalization Given a bound function constexpr auto less_than(auto

    rh) { [f](auto rh) { return [rh](auto lh){return lh < lh) rh;}; return [f,rh](auto { } return f(lh, rh); }; bool all_are_small(std::span<const int> numbers) }; { return std::ranges::all_of(numbers, less_than(10)); } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 23/186
  18. A first generalization constexpr auto less_than(auto rh) { [f](auto rh)

    { return [rh](auto lh){return lh < lh) rh;}; return [f,rh](auto { } return f(lh, rh); }; bool all_are_small(std::span<const int> numbers) }; { Return the lambda that return binds thestd::ranges::all_of(numbers, right hand side argument as well as the less_than(10)); function } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 24/186
  19. A first generalization template <typename F> constexpr auto less_than(auto rh)f

    = {}) { constexpr auto back_binding(F { return [f](auto rh) { return [rh](auto lh){return lh < lh) rh;}; return [f,rh](auto { } return f(lh, rh); }; bool all_are_small(std::span<const int> numbers) }; { } return std::ranges::all_of(numbers, less_than(10)); } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 25/186
  20. A first generalization template <typename F> constexpr auto less_than(auto rh)f

    = {}) { constexpr auto back_binding(F { return [f](auto rh) { return [rh](auto lh){return lh < lh) rh;}; return [f,rh](auto { } return f(lh, rh); }; bool all_are_small(std::span<const int> numbers) A function to call }; { } return std::ranges::all_of(numbers, less_than(10)); } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 26/186
  21. A first generalization Return the lambda that binds the function

    template <typename F> constexpr auto less_than(auto rh)f = {}) { constexpr auto back_binding(F { return [f](auto rh) { return [rh](auto lh){return lh < lh) rh;}; return [f,rh](auto { } return f(lh, rh); }; bool all_are_small(std::span<const int> numbers) }; { } return std::ranges::all_of(numbers, less_than(10)); } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 27/186
  22. A first generalization Return the lambda that binds the function

    template <typename F> constexpr auto less_than(auto rh)f = {}) { constexpr auto back_binding(F { return [f](auto rh) { return [rh](auto lh){return lh < lh) rh;}; return [f,rh](auto { } return f(lh, rh); }; bool all_are_small(std::span<const int> numbers) }; { } return std::ranges::all_of(numbers, less_than(10)); } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 28/186
  23. A first generalization template <typename F> constexpr auto less_than(auto rh)f

    = {}) { constexpr auto back_binding(F { return [f](auto rh) { return [rh](auto lh){return lh < lh) rh;}; return [f,rh](auto { } return f(lh, rh); }; bool all_are_small(std::span<const int> numbers) }; { } return std::ranges::all_of(numbers, less_than(10)); } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 29/186
  24. A first generalization template <typename F> constexpr auto less_than(auto rh)f

    = {}) { constexpr auto back_binding(F { return [f](auto rh) { return [rh](auto lh){return lh < lh) rh;}; return [f,rh](auto { } return f(lh, rh); }; bool all_are_small(std::span<const int> numbers) }; { } https://en.cppreference.com/cpp/utility/functional/bind_back return std::ranges::all_of(numbers, less_than(10)); } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 30/186
  25. A first generalization This is template <typename F> std::bind_back constexpr

    auto less_than(auto rh)f = {}) { constexpr auto back_binding(F { return [f](auto rh) { return [rh](auto lh){return lh < lh) rh;}; return [f,rh](auto { } return f(lh, rh); }; bool all_are_small(std::span<const int> numbers) }; { } return std::ranges::all_of(numbers, less_than(10)); } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 31/186
  26. A first generalization This is template <typename F> std::bind_back constexpr

    auto less_than(auto rh)f = {}) { constexpr auto back_binding(F { return [f](auto rh) { return [rh](auto lh){return lh < lh) rh;}; return [f,rh](auto { } return f(lh, rh); }; bool all_are_small(std::span<const int> numbers) }; { } return std::ranges::all_of(numbers, less_than(10)); } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 32/186
  27. A first generalization template <typename F> constexpr auto less_than(auto rh)f

    = {}) { constexpr auto back_binding(F { return [f](auto rh) { return return [rh](auto lh){return lh < rh;}; std::bind_back(f,rh); } }; } bool all_are_small(std::span<const int> numbers) { return std::ranges::all_of(numbers, less_than(10)); } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 33/186
  28. A first generalization template <typename F> constexpr auto less_than(auto rh)f

    = {}) { constexpr auto back_binding(F { return [f](auto rh) { return return [rh](auto lh){return lh < rh;}; std::bind_back(f,rh); } }; } bool all_are_small(std::span<const int> numbers) { return std::ranges::all_of(numbers, less_than(10)); } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 34/186
  29. A first generalization template <typename F> constexpr auto less_than(auto rh)f

    = {}) { constexpr auto back_binding(F { return [f](auto rh) { return return [rh](auto lh){return lh < rh;}; std::bind_back(f,rh); } }; } bool all_are_small(std::span<const int> numbers) { return std::ranges::all_of(numbers, less_than(10)); } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller Better! @[email protected] 35/186
  30. A first generalization Better! template <typename F> constexpr auto less_than(auto

    rh)f = {}) { constexpr auto back_binding(F { return [f](auto rh) { return return [rh](auto lh){return lh < rh;}; std::bind_back(f,rh); } }; } bool all_are_small(std::span<const int> numbers) { constexpr auto less_than = back_binding<std::less<>>(); return std::ranges::all_of(numbers, less_than(10)); } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 36/186
  31. A first generalization Better! template <typename F> constexpr auto less_than(auto

    rh)f = {}) { constexpr auto back_binding(F { return [f](auto rh) { return return [rh](auto lh){return lh < rh;}; std::bind_back(f,rh); } }; } bool all_are_small(std::span<const int> numbers) { constexpr auto less_than = back_binding<std::less<>>(); return std::ranges::all_of(numbers, less_than(10)); } https://godbolt.org/z/Ez9x5Eq7s Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 37/186
  32. pipes &S::num | less_than(10) Creating Composable Callables in Contemporary C++

    NDC{TechTown} 2026 © Björn Fahller I want to be able to write this @[email protected] 39/186
  33. pipes auto operator|(FL lh, FR rh) Creating Composable Callables in

    Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 40/186
  34. pipes auto operator|(FL lh, FR rh) Need a way to

    identify the types as composable functions Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 41/186
  35. pipes The lambdas will not suffice auto operator|(FL lh, FR

    rh) Need a way to identify the types as composable functions Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 42/186
  36. pipes template <typename F> struct composable_function { F f; };

    Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 43/186
  37. pipes template <typename F> struct composable_function { F f; The

    function to call }; Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 44/186
  38. pipes template <typename F> struct composable_function { F f; template

    <typename ... Ts> decltype(auto) operator()(Ts&& ... ts) const { }; } return f(std::forward<Ts>(ts)...); Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 45/186
  39. pipes template <typename F> struct composable_function { F f; When

    called with arguments template <typename ... Ts> decltype(auto) operator()(Ts&& ... ts) const { }; } return f(std::forward<Ts>(ts)...); Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 46/186
  40. pipes template <typename F> struct composable_function { F f; Call

    the wrapped function template <typename ... Ts> decltype(auto) operator()(Ts&& ... ts) const { }; } return f(std::forward<Ts>(ts)...); Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 47/186
  41. pipes template <typename F> struct composable_function { F f; };

    But only if the function can be called template <typename ... Ts> decltype(auto) operator()(Ts&& ... ts) const requires requires { f(std::forward<Ts>(ts)...);} { return f(std::forward<Ts>(ts)...); } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 48/186
  42. pipes template <typename F> struct back_binding : composable_function<F> { template

    <typename F> struct composable_function { F f; }; }; template <typename ... Ts> decltype(auto) operator()(Ts&& ... ts) const requires requires { f(std::forward<Ts>(ts)...);} { return f(std::forward<Ts>(ts)...); } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 49/186
  43. pipes template <typename F> struct back_binding : composable_function<F> { template

    <typename F> struct composable_function { F f; }; }; back_binding is a template <typename ... Ts> composable_function decltype(auto) operator()(Ts&& ... ts) const requires requires { f(std::forward<Ts>(ts)...);} { return f(std::forward<Ts>(ts)...); } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 50/186
  44. pipes template <typename F> struct back_binding : composable_function<F> { template

    <typename F> using composable_function<F>::operator(); struct composable_function { template <typename ... Ts> auto F f; operator()(Ts&& ... ts) const }; template <typename ... Ts> decltype(auto) operator()(Ts&& ... ts) const requires requires { f(std::forward<Ts>(ts)...);} { return f(std::forward<Ts>(ts)...); } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 51/186
  45. pipes template <typename F> struct back_binding : composable_function<F> { template

    <typename F> using composable_function<F>::operator(); struct composable_function { template <typename ... Ts> auto F f; operator()(Ts&& ... ts) const }; template <typename ... Ts> decltype(auto) operator()(Ts&& ... ts) const requires requires { f(std::forward<Ts>(ts)...);} { It overloads the call return f(std::forward<Ts>(ts)...); operator } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 52/186
  46. pipes template <typename F> struct back_binding : composable_function<F> { template

    <typename F> using composable_function<F>::operator(); struct composable_function { template <typename ... Ts> auto F f; operator()(Ts&& ... ts) const requires(! requires{this->f(std::forward<Ts>(ts)...);}){ template <typename ... Ts> decltype(auto) operator()(Ts&& ... ts) const requires requires { f(std::forward<Ts>(ts)...);} { return f(std::forward<Ts>(ts)...); } }; } }; Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 53/186
  47. pipes template <typename F> struct back_binding : composable_function<F> { template

    <typename F> using composable_function<F>::operator(); struct composable_function { template <typename ... Ts> auto F f; operator()(Ts&& ... ts) const requires(! requires{this->f(std::forward<Ts>(ts)...);}){ template <typename ... Ts> decltype(auto) operator()(Ts&& ... ts) const requires requires { f(std::forward<Ts>(ts)...);} { return f(std::forward<Ts>(ts)...); But only when the underlying function } cannot be called }; } }; Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 54/186
  48. pipes template <typename F> struct back_binding : composable_function<F> { template

    <typename F> using composable_function<F>::operator(); struct composable_function { template <typename ... Ts> auto F f; operator()(Ts&& ... ts) const requires(! requires{this->f(std::forward<Ts>(ts)...);}){ auto bound = std::bind_back( template <typename ... Ts> this->f, decltype(auto) operator()(Ts&& ... ts) const std::forward<Ts>(ts)...); requires requires { f(std::forward<Ts>(ts)...);} { using RT = back_binding<decltype(bound)>; return f(std::forward<Ts>(ts)...); } return RT{std::move(bound)}; }; } }; Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 55/186
  49. pipes template <typename F> struct back_binding : composable_function<F>Bind { the

    template <typename F> arguments at the using composable_function<F>::operator(); struct composable_function back { template <typename ... Ts> auto F f; operator()(Ts&& ... ts) const requires(! requires{this->f(std::forward<Ts>(ts)...);}){ auto bound = std::bind_back( template <typename ... Ts> this->f, decltype(auto) operator()(Ts&& ... ts) const std::forward<Ts>(ts)...); requires requires { f(std::forward<Ts>(ts)...);} { using RT = back_binding<decltype(bound)>; return f(std::forward<Ts>(ts)...); } return RT{std::move(bound)}; }; } }; Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 56/186
  50. pipes template <typename F> struct back_binding : composable_function<F> { template

    <typename F> using composable_function<F>::operator(); struct composable_function { template <typename ... Ts> auto F f; operator()(Ts&& ... ts) const Return another requires(! requires{this->f(std::forward<Ts>(ts)...);}){ back_binding auto bound = std::bind_back( template <typename ... Ts> this->f, decltype(auto) operator()(Ts&& ... ts) const std::forward<Ts>(ts)...); requires requires { f(std::forward<Ts>(ts)...);} { using RT = back_binding<decltype(bound)>; return f(std::forward<Ts>(ts)...); } return RT{std::move(bound)}; }; } }; Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 57/186
  51. pipes template <typename F> struct composable_function { ... }; template

    <typename F> struct back_binding : composable_function<F> { ... }; constexpr auto less_than = back_binding<std::less<>>(); Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 58/186
  52. pipes Usage is the same as before template <typename F>

    struct composable_function { ... }; template <typename F> struct back_binding : composable_function<F> { ... }; constexpr auto less_than = back_binding<std::less<>>(); Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 59/186
  53. pipes Usage is the same as before template <typename F>

    struct composable_function { ... }; template <typename F> struct back_binding : composable_function<F> { ... }; constexpr auto less_than = back_binding<std::less<>>(); https://godbolt.org/z/166KG4x5b Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 60/186
  54. pipes template <typename F> struct composable_function; template <typename F> concept

    composable_function_type = requires { }; Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 61/186
  55. pipes template <typename F> struct composable_function; Create a concept that

    can be used by the pipe operator template <typename F> concept composable_function_type = requires { }; Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 62/186
  56. pipes template <typename F> struct composable_function; template <typename F> concept

    composable_function_type = requires { []<typename T>(composable_function<T>*){}( std::declval<std::remove_cvref_t<F>*>() ); }; Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 63/186
  57. pipes template <typename F> struct composable_function; Require that a pointer

    to type F template <typename F> concept composable_function_type = requires { []<typename T>(composable_function<T>*){}( std::declval<std::remove_cvref_t<F>*>() ); }; Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 64/186
  58. pipes template <typename F> struct composable_function; Can be passed to

    a function accepting a pointer to a composable_function template <typename F> concept composable_function_type = requires { []<typename T>(composable_function<T>*){}( std::declval<std::remove_cvref_t<F>*>() ); }; Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 65/186
  59. pipes template <typename F> struct composable_function; template <typename F> This

    matches concept composable_function_type = requires {back_binding too since it models an []<typename T>(composable_function<T>*){}( is-A relationship std::declval<std::remove_cvref_t<F>*>() through inheritance ); }; Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 66/186
  60. pipes template < composable_function_type LH, composable_function_type RH> auto operator|(LH&& lh,

    RH&& rh) { } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 67/186
  61. pipes template < composable_function_type LH, composable_function_type RH> auto operator|(LH&& lh,

    RH&& rh) { Now we can write the pipe between two composable functions. } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 68/186
  62. pipes template < composable_function_type LH, composable_function_type RH> auto operator|(LH&& lh,

    RH&& rh) { auto pipe = [lh = std::forward<LH>(lh), rh = std::forward<RH>(rh)] <typename ... Ts>(Ts&& ... ts) { return rh(lh(std::forward<Ts>(ts)...)); }; } return RT{std::move(pipe)}; Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 69/186
  63. pipes template < Capture the composable_function_type LH, functions composable_function_type RH>

    auto operator|(LH&& lh, RH&& rh) { auto pipe = [lh = std::forward<LH>(lh), rh = std::forward<RH>(rh)] <typename ... Ts>(Ts&& ... ts) { return rh(lh(std::forward<Ts>(ts)...)); }; } return RT{std::move(pipe)}; Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 70/186
  64. pipes template < composable_function_type LH, composable_function_type RH> Call the left

    function auto operator|(LH&& lh, RH&& rh) with the arguments { to the pipe auto pipe = [lh = std::forward<LH>(lh), rh = std::forward<RH>(rh)] <typename ... Ts>(Ts&& ... ts) { return rh(lh(std::forward<Ts>(ts)...)); }; } return RT{std::move(pipe)}; Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 71/186
  65. pipes template < composable_function_type LH, composable_function_type RH> auto operator|(LH&& lh,

    RH&& rh) { auto pipe = [lh = std::forward<LH>(lh), rh = std::forward<RH>(rh)] <typename ... Ts>(Ts&& ... ts) { return rh(lh(std::forward<Ts>(ts)...)); }; } return Call the right function with the RT{std::move(pipe)}; result of the left Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 72/186
  66. pipes template < composable_function_type LH, composable_function_type RH> auto operator|(LH&& lh,

    RH&& rh) { But what auto pipe = [lh = std::forward<LH>(lh), should the rh = std::forward<RH>(rh)] return type be? <typename ... Ts>(Ts&& ... ts) { return rh(lh(std::forward<Ts>(ts)...)); }; } return RT{std::move(pipe)}; Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 73/186
  67. pipes template < composable_function_type LH, composable_function_type RH> auto operator|(LH&& lh,is

    called RH&& rh) The pipe the same way as { But what the left=function, so auto pipe = [lh std::forward<LH>(lh), should the it must be of the rhsame = std::forward<RH>(rh)] return type be? kind. <typename ... Ts>(Ts&& ... ts) { return rh(lh(std::forward<Ts>(ts)...)); }; } return RT{std::move(pipe)}; Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 74/186
  68. pipes template < LH is a specialization of a composable

    function composable_function_type LH, type. composable_function_type RH> auto operator|(LH&& lh, RH&& rh) C<F> { We need the same kind auto pipe = [lh = std::forward<LH>(lh).f, of template, but rh = std::forward<RH>(rh).f] instantiated with our pipe. <typename ... Ts>(Ts&& ... ts) { return rh(lh(std::forward<Ts>(ts)...)); }; using RT = rebind_template_t<std::remove_cvref_t<LH>, decltype(pipe)>; return RT{std::move(pipe)}; } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 75/186
  69. pipes template <typename> < template LH is a specialization of

    a composable function composable_function_type LH, struct rebind_template; type. composable_function_type RH> auto operator|(LH&& lh, RH&& rh) C<F> { We need the same kind auto pipe = [lh = std::forward<LH>(lh).f, of template, but rh = std::forward<RH>(rh).f] instantiated with our pipe. <typename ... Ts>(Ts&& ... ts) { return rh(lh(std::forward<Ts>(ts)...)); }; using RT = rebind_template_t<std::remove_cvref_t<LH>, decltype(pipe)>; return RT{std::move(pipe)}; } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 76/186
  70. pipes template <typename> < template LH is a specialization of

    a composable function composable_function_type LH, struct rebind_template; type. composable_function_type RH> auto operator|(LH&& lh, RH&& rh) template <template <typename> class T, typename Original> C<F> { struct rebind_template<T<Original>> We need the same kind auto pipe = [lh = std::forward<LH>(lh).f, { of template, but rh = std::forward<RH>(rh).f] instantiated with our pipe. <typename ... Ts>(Ts&& ... ts) { return rh(lh(std::forward<Ts>(ts)...)); }; }; using RT = rebind_template_t<std::remove_cvref_t<LH>, decltype(pipe)>; return RT{std::move(pipe)}; } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 77/186
  71. pipes template <typename> < template LH is a specialization of

    a composable function composable_function_type LH, struct rebind_template; type. composable_function_type RH> auto operator|(LH&& lh, RH&& rh) template <template <typename> class T, typename Original> C<F> { struct rebind_template<T<Original>> We need the same kind auto pipe = [lh = std::forward<LH>(lh).f, { of template, but rh = std::forward<RH>(rh).f] instantiated with our pipe. <typename ... Ts>(Ts&& ... ts) { When instantiated with a return rh(lh(std::forward<Ts>(ts)...)); }; template T, instantiated }; with type Original, i.e. T<Original> using RT = rebind_template_t<std::remove_cvref_t<LH>, decltype(pipe)>; return RT{std::move(pipe)}; } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 78/186
  72. pipes template <typename> < template LH is a specialization of

    a composable function composable_function_type LH, struct rebind_template; type. composable_function_type RH> auto operator|(LH&& lh, RH&& rh) template <template <typename> class T, typename Original> C<F> { struct rebind_template<T<Original>> We need the same kind auto pipe = [lh = std::forward<LH>(lh).f, { of template, but rh = std::forward<RH>(rh).f] template <typename Substitute> instantiated with our pipe. <typename Ts>(Ts&& ... ts) { using with ... = T<Substitute>; return rh(lh(std::forward<Ts>(ts)...)); }; }; using RT = rebind_template_t<std::remove_cvref_t<LH>, decltype(pipe)>; return RT{std::move(pipe)}; } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 79/186
  73. pipes template <typename> < template LH is a specialization of

    a composable function composable_function_type LH, struct rebind_template; type. composable_function_type RH> auto operator|(LH&& lh, RH&& rh) template <template <typename> class T, typename Original> C<F> { struct rebind_template<T<Original>> We need the same kind auto pipe = [lh = std::forward<LH>(lh).f, { of template, but rh = std::forward<RH>(rh).f] template <typename Substitute> instantiated with our pipe. <typename Ts>(Ts&& ... ts) { using with ... = T<Substitute>; return rh(lh(std::forward<Ts>(ts)...)); }; }; using RT = rebind_template_t<std::remove_cvref_t<LH>, We offer an alias which isdecltype(pipe)>; the template return RT{std::move(pipe)}; T instantiated with a substitute } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 80/186
  74. pipes template <typename> < template LH is a specialization of

    a composable function composable_function_type LH, struct rebind_template; type. composable_function_type RH> auto operator|(LH&& lh, RH&& rh) template <template <typename> class T, typename Original> C<F> { struct rebind_template<T<Original>> And, finally, an the same kind We need auto pipe = [lh = std::forward<LH>(lh).f, { alias forof less template, but rh = std::forward<RH>(rh).f] template <typename Substitute> verboseinstantiated use with our pipe. <typename Ts>(Ts&& ... ts) { using with ... = T<Substitute>; return rh(lh(std::forward<Ts>(ts)...)); }; }; using<typename RT = rebind_template_t<std::remove_cvref_t<LH>, template T, typename S> decltype(pipe)>; using rebind_template_t = return RT{std::move(pipe)}; rebind_template<T>::template with<S>; } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 81/186
  75. pipes template < composable_function_type LH, composable_function_type RH> auto operator|(LH&& lh,

    RH&& rh) { auto pipe = [lh = std::forward<LH>(lh).f, rh = std::forward<RH>(rh).f] <typename ... Ts>(Ts&& ... ts) { return rh(lh(std::forward<Ts>(ts)...)); }; using RT = rebind_template_t<std::remove_cvref_t<LH>, decltype(pipe)>; return RT{std::move(pipe)}; } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 82/186
  76. pipes struct S { template < int num; composable_function_type LH,

    std::string_view name; composable_function_type RH> }; auto operator|(LH&& lh, RH&& rh) { constexpr auto =get_num = auto pipe [lh = std::forward<LH>(lh).f, composable_function{std::mem_fn(&S::num)}; rh = std::forward<RH>(rh).f] <typename ... Ts>(Ts&& ... ts) { bool all_are_small(std::span<const S> numbers) return rh(lh(std::forward<Ts>(ts)...)); { }; return std::ranges::all_of(numbers, using RT = rebind_template_t<std::remove_cvref_t<LH>, get_num | less_than(10)); decltype(pipe)>; } return RT{std::move(pipe)}; } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 83/186
  77. pipes Composable function get_num, which when called with an S

    will return the member num struct S { template < int num; composable_function_type LH, std::string_view name; composable_function_type RH> }; auto operator|(LH&& lh, RH&& rh) { constexpr auto =get_num = auto pipe [lh = std::forward<LH>(lh).f, composable_function{std::mem_fn(&S::num)}; rh = std::forward<RH>(rh).f] <typename ... Ts>(Ts&& ... ts) { bool all_are_small(std::span<const S> numbers) return rh(lh(std::forward<Ts>(ts)...)); { }; return std::ranges::all_of(numbers, using RT = rebind_template_t<std::remove_cvref_t<LH>, get_num | less_than(10)); decltype(pipe)>; } return RT{std::move(pipe)}; } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 84/186
  78. pipes struct S { template < int num; composable_function_type LH,

    std::string_view name; composable_function_type RH> }; auto operator|(LH&& lh, RH&& rh) { constexpr auto =get_num = auto pipe [lh = std::forward<LH>(lh).f, composable_function{std::mem_fn(&S::num)}; rh = std::forward<RH>(rh).f] And it can be used in a pipe <typename ... Ts>(Ts&& ... ts) { bool all_are_small(std::span<const S> numbers) return rh(lh(std::forward<Ts>(ts)...)); { }; return std::ranges::all_of(numbers, using RT = rebind_template_t<std::remove_cvref_t<LH>, get_num | less_than(10)); decltype(pipe)>; } return RT{std::move(pipe)}; } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 85/186
  79. pipes struct S { template < int num; composable_function_type LH,

    std::string_view name; composable_function_type RH> }; auto operator|(LH&& lh, RH&& rh) { constexpr auto =get_num = auto pipe [lh = std::forward<LH>(lh).f, composable_function{std::mem_fn(&S::num)}; rh = std::forward<RH>(rh).f] <typename ... Ts>(Ts&& ... ts) { bool all_are_small(std::span<const S> numbers) return rh(lh(std::forward<Ts>(ts)...)); { }; return std::ranges::all_of(numbers, using RT = rebind_template_t<std::remove_cvref_t<LH>, get_num | less_than(10)); decltype(pipe)>; } return RT{std::move(pipe)}; } https://godbolt.org/z/GbKqraE69 Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 86/186
  80. pipes template < composable_function_type LH, composable_function_type RH> auto operator|(LH&& lh,

    RH&& rh); template <typename T, typename C, composable_function_type F> auto operator|(T (C::*member), F&& f) { return composable_function{std::mem_fn(member)} | std::forward<F>(f); Two tiny convenience } functions template <composable_function_type F, typename T, typename C> auto operator|(F&& f, T (C::*member)) { return std::forward<F>(f) | composable_function{std::mem_fn(member)}; } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 87/186
  81. pipes struct S< { template int num; composable_function_type LH, composable_function_type

    RH> std::string_view auto operator|(LH&& lh,name; RH&& rh); }; template <typename T, typename C, composable_function_type F> auto (C::*member), F&& f) S> { numbers) booloperator|(T all_are_small(std::span<const { return composable_function{std::mem_fn(member)} | std::forward<F>(f); return std::ranges::all_of(numbers, } &S::num | less_than(10)); } template <composable_function_type F, typename T, typename C> auto operator|(F&& f, T (C::*member)) { return std::forward<F>(f) | composable_function{std::mem_fn(member)}; } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 88/186
  82. pipes struct S< { template int num; composable_function_type LH, composable_function_type

    RH> std::string_view auto operator|(LH&& lh,name; RH&& rh); }; More convenient template <typename T, typename C, composable_function_type F> short hand auto (C::*member), F&& f) S> { numbers) booloperator|(T all_are_small(std::span<const { return composable_function{std::mem_fn(member)} | std::forward<F>(f); return std::ranges::all_of(numbers, } &S::num | less_than(10)); } template <composable_function_type F, typename T, typename C> auto operator|(F&& f, T (C::*member)) { return std::forward<F>(f) | composable_function{std::mem_fn(member)}; } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 89/186
  83. pipes struct S< { template int num; composable_function_type LH, composable_function_type

    RH> std::string_view auto operator|(LH&& lh,name; RH&& rh); }; template <typename T, typename C, composable_function_type F> auto (C::*member), F&& f) S> { numbers) booloperator|(T all_are_small(std::span<const { return composable_function{std::mem_fn(member)} | std::forward<F>(f); return std::ranges::all_of(numbers, } &S::num | less_than(10)); } template <composable_function_type F, typename T, typename C> auto operator|(F&& f, T (C::*member)) { return std::forward<F>(f) | composable_function{std::mem_fn(member)}; } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 90/186
  84. transform_args How do I, e.g., sort on a member? Creating

    Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 91/186
  85. transform_args template <typename Proj, composable_function_type F> constexpr auto transform_args(Proj&& proj,

    F&& f) { } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 92/186
  86. transform_args template <typename Proj, composable_function_type F> constexpr auto transform_args(Proj&& proj,

    F&& f) { Create transform_args, which takes a projection and a function, and applies the projection to each argument to thefunction } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 93/186
  87. transform_args template <typename Proj, composable_function_type F> constexpr auto transform_args(Proj&& proj,

    F&& f) { auto transform = [proj = std::forward<Proj>(proj), f = std::forward<F>(f).f] <typename ... Ts>(Ts&& ... ts) { return f(proj(std::forward<Ts>(ts))...); }; using RT = rebind_template_t<std::remove_cvref_t<F>>, decltype(transform)>; return RT{std::move(transform)}; } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 94/186
  88. transform_args template <typename Proj, composable_function_type F> constexpr auto transform_args(Proj&& proj,

    F&& f) { auto transform = [proj = std::forward<Proj>(proj), f = std::forward<F>(f).f] <typename ... Ts>(Ts&& ... ts) { Bind the projection return f(proj(std::forward<Ts>(ts))...); and the function }; using RT = rebind_template_t<std::remove_cvref_t<F>>, decltype(transform)>; return RT{std::move(transform)}; } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 95/186
  89. transform_args template <typename Proj, composable_function_type F> constexpr auto transform_args(Proj&& proj,

    F&& f) { auto transform = Transform each [proj = std::forward<Proj>(proj), argument through the f = std::forward<F>(f).f] projection in the call <typename ... Ts>(Ts&& ... ts) { return f(proj(std::forward<Ts>(ts))...); }; using RT = rebind_template_t<std::remove_cvref_t<F>>, decltype(transform)>; return RT{std::move(transform)}; } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 96/186
  90. transform_args template <typename Proj, composable_function_type F> constexpr auto transform_args(Proj&& proj,

    F&& f) { auto transform = [proj = std::forward<Proj>(proj), f = std::forward<F>(f).f] <typename ... Ts>(Ts&& ... ts) { return f(proj(std::forward<Ts>(ts))...); }; using RT = rebind_template_t<std::remove_cvref_t<F>>, decltype(transform)>; return RT{std::move(transform)}; } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 97/186
  91. transform_args template <typename Proj, composable_function_type F> Same as constexpr auto

    transform_args(Proj&& proj, F&& f) pipe? { auto transform = [proj = std::forward<Proj>(proj), f = std::forward<F>(f).f] <typename ... Ts>(Ts&& ... ts) { return f(proj(std::forward<Ts>(ts))...); }; using RT = rebind_template_t<std::remove_cvref_t<F>>, decltype(transform)>; return RT{std::move(transform)}; } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 98/186
  92. transform_args template <typename Proj, composable_function_type F> Same as constexpr auto

    transform_args(Proj&& proj, F&& f) pipe? { auto transform = [proj = std::forward<Proj>(proj), f = std::forward<F>(f).f] <typename ... Ts>(Ts&& ... ts) { return f(proj(std::forward<Ts>(ts))...); }; auto pipe = [lh = std::forward<LH>(lh).f, using RT = rebind_template_t<std::remove_cvref_t<F>>, rh = std::forward<RH>(rh).f] decltype(transform)>; <typename ... Ts>(Ts&& ... ts) { return RT{std::move(transform)}; return rh(lh(std::forward<Ts>(ts)...)); } }; Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 99/186
  93. transform_args template <typename Proj, composable_function_type F> constexpr auto transform_args(Proj&& proj,

    F&& f) { auto transform = The pipe passes all [proj = std::forward<Proj>(proj), arguments to the f = std::forward<F>(f).f] left hand side <typename ... Ts>(Ts&& ... ts) function { return f(proj(std::forward<Ts>(ts))...); }; auto pipe = [lh = std::forward<LH>(lh).f, using RT = rebind_template_t<std::remove_cvref_t<F>>, rh = std::forward<RH>(rh).f] decltype(transform)>; <typename ... Ts>(Ts&& ... ts) { return RT{std::move(transform)}; return rh(lh(std::forward<Ts>(ts)...)); } }; Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 100/186
  94. transform_args transform_args calls the projection template <typename Proj, composable_function_type F>

    function on each constexpr auto transform_args(Proj&& proj, F&& f) argument { } auto transform = [proj = std::forward<Proj>(proj), f = std::forward<F>(f).f] <typename ... Ts>(Ts&& ... ts) { return f(proj(std::forward<Ts>(ts))...); }; auto pipe = [lh = std::forward<LH>(lh).f, using RT = rebind_template_t<std::remove_cvref_t<F>>, rh = std::forward<RH>(rh).f] decltype(transform)>; <typename ... Ts>(Ts&& ... ts) { return RT{std::move(transform)}; return rh(lh(std::forward<Ts>(ts)...)); }; Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 101/186
  95. transform_args template <typename Proj, composable_function_type F> constexpr auto transform_args(Proj&& proj,

    F&& f) {...} template <typename T, typename C, composable_function_type F> constexpr auto transform_args(T (C::*member), F&& f) { return transform_args(composable_function(member), std::forward<F>(f)); } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 102/186
  96. transform_args template <typename Proj, composable_function_type F> Create a convenience constexpr

    auto transform_args(Proj&& proj, F&&forf) wrapper pointer to {...} member template <typename T, typename C, composable_function_type F> constexpr auto transform_args(T (C::*member), F&& f) { return transform_args(composable_function(member), std::forward<F>(f)); } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 103/186
  97. transform_argssetButisthisnotoverload an object that can be passed template <typename Proj,

    composable_function_type F> constexpr auto transform_args(Proj&& proj, F&& f) {...} template <typename T, typename C, composable_function_type F> constexpr auto transform_args(T (C::*member), F&& f) { return transform_args(composable_function(member), std::forward<F>(f)); } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 104/186
  98. transform_args namespace impl { template <typename Proj, composable_function_type F> constexpr

    auto transform_args(Proj&& proj, F&& f) {...} template <typename T, typename C, composable_function_type F> constexpr auto transform_args(T (C::*member), F&& f) { return transform_args(composable_function(member), std::forward<F>(f)); } } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 105/186
  99. transform_args Add another level of indirection namespace impl { template

    <typename Proj, composable_function_type F> constexpr auto transform_args(Proj&& proj, F&& f) {...} template <typename T, typename C, composable_function_type F> constexpr auto transform_args(T (C::*member), F&& f) { return transform_args(composable_function(member), std::forward<F>(f)); } } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 106/186
  100. transform_args namespace impl { template <typename Proj, composable_function_type F> constexpr

    auto transform_args(Proj&& proj, F&& f) {...} template <typename T, typename C, composable_function_type F> constexpr auto transform_args(T (C::*member), F&& f) { inline return constexpr auto transform_args = front_binding { transform_args(composable_function(member), []<typename ... Ts>(Ts&& ...std::forward<F>(f)); ts) -> }decltype(impl::transform_args(std::forward<Ts>(ts)...)) { } return impl::transform_args(std::forward<Ts>(ts)...); } }; Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 107/186
  101. transform_args namespace impl { template <typename Proj, composable_function_type F> constexpr

    auto transform_args(Proj&& proj, F&& f) {...} template <typename T, typename C, composable_function_type F> constexpr auto transform_args(T (C::*member), F&& f) { inline return constexpr auto transform_args = front_binding { transform_args(composable_function(member), []<typename ... Ts>(Ts&& ...std::forward<F>(f)); ts) -> }decltype(impl::transform_args(std::forward<Ts>(ts)...)) { } return impl::transform_args(std::forward<Ts>(ts)...); } }; Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 108/186
  102. transform_args namespace impl { template <typename Proj, composable_function_type F> constexpr

    auto transform_args(Proj&& proj, F&& f) {...} template <typename T, typename C, composable_function_type F> constexpr auto transform_args(T (C::*member), F&& f) { inline return constexpr auto transform_args = front_binding { transform_args(composable_function(member), []<typename ... Ts>(Ts&& ...std::forward<F>(f)); ts) -> }decltype(impl::transform_args(std::forward<Ts>(ts)...)) { } return impl::transform_args(std::forward<Ts>(ts)...); } }; Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 109/186
  103. front_binding template <typename F> struct front_binding : composable_function<F> { using

    composable_function<F>::operator(); template <typename ... Ts> constexpr auto operator()(Ts&& ... ts) const requires(! requires{this->f(std::forward<Ts>(ts)...);}){ auto bound = std::bind_front( this->f, std::forward<Ts>(ts)...); using RT = front_binding<decltype(bound)>; return RT{std::move(bound)}; } }; Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 110/186
  104. front_binding template <typename F> struct front_binding : composable_function<F> { Same

    as back_binding, but using composable_function<F>::operator(); calls std::bind_front template <typename ... Ts> instead of constexpr auto operator()(Ts&& ... ts) const std::bind_back requires(! requires{this->f(std::forward<Ts>(ts)...);}){ auto bound = std::bind_front( this->f, std::forward<Ts>(ts)...); using RT = front_binding<decltype(bound)>; return RT{std::move(bound)}; } }; Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 111/186
  105. front_binding template <typename F> struct front_binding : composable_function<F> { using

    composable_function<F>::operator(); template <typename ... Ts> constexpr auto operator()(Ts&& ... ts) const requires(! requires{this->f(std::forward<Ts>(ts)...);}){ auto bound = std::bind_front( this->f, std::forward<Ts>(ts)...); using RT = front_binding<decltype(bound)>; return RT{std::move(bound)}; } }; Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 112/186
  106. transform_args struct S { int num; std::string_view name; }; inline

    constexpr auto by_num = transform_args(&S::num); bool are_sorted_by_num(std::span<const S> numbers) { return std::ranges::is_sorted(numbers, by_num(less_than)); } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 113/186
  107. transform_args struct S { int num; std::string_view name; }; inline

    constexpr auto by_num = transform_args(&S::num); bool are_sorted_by_num(std::span<const S> numbers) { return std::ranges::is_sorted(numbers, by_num(less_than)); } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 114/186
  108. transform_args struct S { int num; std::string_view name; }; inline

    constexpr auto by_num = transform_args(&S::num); bool are_sorted_by_num(std::span<const S> numbers) { return std::ranges::is_sorted(numbers, by_num(less_than)); } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 115/186
  109. transform_args struct S { int num; std::string_view name; }; inline

    constexpr auto by_num = transform_args(&S::num); bool are_sorted_by_num(std::span<const S> numbers) { return std::ranges::is_sorted(numbers, by_num(less_than)); } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 116/186
  110. transform_args inline constexpr auto is_sorted = back_binding{[]<typename ... Ts>(Ts&& ...

    ts) -> decltype(std::ranges::is_sorted(std::forward<Ts>(ts)...)){ return std::ranges::is_sorted(std::forward<Ts>(ts)...); } }; inline constexpr auto is_sorted_by_num(composable_function_type auto order){ return is_sorted(by_num(order)); } bool are_sorted_by_num(std::span<const S> numbers) { return is_sorted_by_num(less_than)(numbers); https://godbolt.org/z/oxb86xn1Y } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 117/186
  111. transform_args inline constexpr auto is_sorted = back_binding{[]<typename ... Ts>(Ts&& ...

    ts) -> decltype(std::ranges::is_sorted(std::forward<Ts>(ts)...)){ return std::ranges::is_sorted(std::forward<Ts>(ts)...); } }; inline constexpr auto back_binding wrapper for is_sorted_by_num(composable_function_type auto order){ std::ranges::is_sorted return is_sorted(by_num(order)); } bool are_sorted_by_num(std::span<const S> numbers) { return is_sorted_by_num(less_than)(numbers); https://godbolt.org/z/oxb86xn1Y } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 118/186
  112. transform_args inline constexpr auto is_sorted = back_binding{[]<typename ... Ts>(Ts&& ...

    ts) -> decltype(std::ranges::is_sorted(std::forward<Ts>(ts)...)){ Sort on &S::num with return std::ranges::is_sorted(std::forward<Ts>(ts)...); order as argument } }; inline constexpr auto is_sorted_by_num(composable_function_type auto order){ return is_sorted(by_num(order)); } bool are_sorted_by_num(std::span<const S> numbers) { return is_sorted_by_num(less_than)(numbers); https://godbolt.org/z/oxb86xn1Y } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 119/186
  113. transform_args inline constexpr auto is_sorted = back_binding{[]<typename ... Ts>(Ts&& ...

    ts) -> decltype(std::ranges::is_sorted(std::forward<Ts>(ts)...)){ return std::ranges::is_sorted(std::forward<Ts>(ts)...); } }; inline constexpr auto Sorted in increasing order is_sorted_by_num(composable_function_type autousing order){ less_than return is_sorted(by_num(order)); } bool are_sorted_by_num(std::span<const S> numbers) { return is_sorted_by_num(less_than)(numbers); https://godbolt.org/z/oxb86xn1Y } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 120/186
  114. transform_args inline constexpr auto is_sorted = back_binding{[]<typename ... Ts>(Ts&& ...

    ts) -> decltype(std::ranges::is_sorted(std::forward<Ts>(ts)...)){ return std::ranges::is_sorted(std::forward<Ts>(ts)...); } }; inline constexpr auto is_sorted_by_num(composable_function_type auto order){ return is_sorted(by_num(order)); } bool are_sorted_by_num(std::span<const S> numbers) { return is_sorted_by_num(less_than)(numbers); https://godbolt.org/z/oxb86xn1Y } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 121/186
  115. perfect(er) forwarding What if I want to bind something that

    is not copyable? Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 122/186
  116. perfect(er) forwarding void consume(int, std::unique_ptr<int>); int main() { auto bc

    = back_binding{consume}; auto consume_ptr = bc(std::make_unique<int>(3)); consume_ptr(3); } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 123/186
  117. perfect(er) forwarding void consume(int, std::unique_ptr<int>); Outrageous int main() compilation error

    { message! auto bc = back_binding{consume}; auto consume_ptr = bc(std::make_unique<int>(3)); consume_ptr(3); } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 124/186
  118. perfect(er) forwarding void consume(int, std::unique_ptr<int>); Outrageous compilation error message! int

    main() { auto bc = back_binding{consume}; clang: 97 lines auto consume_ptr = bc(std::make_unique<int>(3)); consume_ptr(3); } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 125/186
  119. perfect(er) forwarding void consume(int, std::unique_ptr<int>); Outrageous compilation error message! int

    main() { auto bc = back_binding{consume}; clang: 97 lines auto consume_ptr = bc(std::make_unique<int>(3)); gcc: 34 lines consume_ptr(3); } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 126/186
  120. perfect(er) forwarding void consume(int, std::unique_ptr<int>); Outrageous compilation error message! int

    main() { auto bc = back_binding{consume}; clang: 97 lines auto consume_ptr = bc(std::make_unique<int>(3)); gcc: 34 lines consume_ptr(3); MSVC: 11 lines } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 127/186
  121. perfect(er) forwarding void consume(int, std::unique_ptr<int>); Outrageous compilation error message! int

    main() { auto bc = back_binding{consume}; clang: 97 lines autocallconsume_ptr = bc(std::make_unique<int>(3)); All function gcc: 34 lines operatorsconsume_ptr(3); are const, MSVC: 11 lines so}it passes a const unique_ptr in the call. Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 128/186
  122. perfect(er) forwarding template <typename F> struct composable_function { F f;

    }; template <typename Self, typename ... Ts> constexpr auto operator()(this Self&& self, Ts&&...ts) -> decltype(std::forward<Self>(self).f (std::forward<Ts>(ts)...)) { return std::forward<Self>(self).f (std::forward<Ts>(ts)...); https://godbolt.org/z/1qGaEvjM6 } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 129/186
  123. perfect(er) forwarding template <typename F> struct composable_function { F f;

    }; C++20 “deducing this” or “explicit object parameter” template <typename Self, typename ... Ts> constexpr auto operator()(this Self&& self, Ts&&...ts) -> decltype(std::forward<Self>(self).f (std::forward<Ts>(ts)...)) { return std::forward<Self>(self).f (std::forward<Ts>(ts)...); https://godbolt.org/z/1qGaEvjM6 } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 130/186
  124. perfect(er) forwarding template <typename F> struct composable_function { F f;

    }; template <typename Self, typename ... Ts> constexpr auto operator()(this Self&& self, Ts&&...ts) -> decltype(std::forward<Self>(self).f (std::forward<Ts>(ts)...)) { Forwards f as: return std::forward<Self>(self).f F&& or (std::forward<Ts>(ts)...); F& or const F&& or https://godbolt.org/z/1qGaEvjM6 } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller const F& @[email protected] 131/186
  125. perfect(er) forwarding template <typename F> struct composable_function { F f;

    }; template <typename Self, typename ... Ts> constexpr auto operator()(this Self&& self, Ts&&...ts) Repeat for every -> and decltype(std::forward<Self>(self).f operator() (std::forward<Ts>(ts)...)) every lambda { return std::forward<Self>(self).f (std::forward<Ts>(ts)...); https://godbolt.org/z/1qGaEvjM6 } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 132/186
  126. perfect(er) forwarding void consume(int, std::unique_ptr<int>); int main() { auto bc

    = back_binding{consume}; auto consume_ptr = bc(std::make_unique<int>(3)); consume_ptr(3); } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 133/186
  127. perfect(er) forwarding void consume(int, std::unique_ptr<int>); Outrageous compilation error message! int

    main() { auto bc = back_binding{consume}; clang: 97 lines auto consume_ptr = bc(std::make_unique<int>(3)); gcc: 34 lines consume_ptr(3); MSVC: 14 lines } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 134/186
  128. perfect(er) forwarding void consume(int, std::unique_ptr<int>); int main() Only gcc give’s

    a { reasonable hint, auto bc = back_binding{consume}; pointing to auto consume_ptr = consume_ptr(3); bc(std::make_unique<int>(3)); consume_ptr(3); } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 135/186
  129. perfect(er) forwarding void consume(int, std::unique_ptr<int>); int main() { auto bc

    = back_binding{consume}; auto consume_ptr = bc(std::make_unique<int>(3)); std::move(consume_ptr)(3); } https://godbolt.org/z/7MeGqaxzK Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 136/186
  130. perfect(er) forwarding void consume(int, std::unique_ptr<int>); int main() { auto bc

    = back_binding{consume}; auto consume_ptr = bc(std::make_unique<int>(3)); std::move(consume_ptr)(3); } https://godbolt.org/z/7MeGqaxzK Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 137/186
  131. perfect(er) forwarding void consume(int, std::unique_ptr<int>); int main() { auto bc

    = back_binding{consume}; auto consume_ptr = bc(std::make_unique<int>(3)); std::move(consume_ptr)(3); } OK I guess... https://godbolt.org/z/7MeGqaxzK Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 138/186
  132. Ban std::ranges::dangling Functions that return ranges::borrowed_iterator_t or ranges::borrowed_subrange_t will instead

    return std::ranges::dangling if they are called with an r-value range. Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 139/186
  133. Ban std::ranges::dangling Functions that return ranges::borrowed_iterator_t or ranges::borrowed_subrange_t will instead

    return std::ranges::dangling if they are called with an r-value range. When did you last look at the return value from sort()? Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 140/186
  134. Ban std::ranges::dangling template <typename T> inline constexpr bool not_dangling =

    true; template <> inline constexpr bool not_dangling<std::ranges::dangling> = false; Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 141/186
  135. Ban std::ranges::dangling template <typename T> inline constexpr bool not_dangling =

    true; template <> inline constexpr bool not_dangling<std::ranges::dangling> = false; template <template <typename...> class T, typename ... Ts> inline constexpr bool not_dangling<T<Ts...>> = (not_dangling<Ts> && ...); Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 142/186
  136. Ban std::ranges::dangling template <typename T> inline constexpr bool not_dangling =

    true;std::ranges::mismatch() returns template <> mismatch_result<borrowed_iterator_t<R1>, inline constexpr bool borrowed_iterator_t<R2>> not_dangling<std::ranges::dangling> = false; template <template <typename...> class T, typename ... Ts> inline constexpr bool not_dangling<T<Ts...>> = (not_dangling<Ts> && ...); Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 143/186
  137. Ban std::ranges::dangling template <typename T> inline constexpr bool not_dangling =

    true; We can now use a concept to constraing the calls bool template <> inline constexpr not_dangling<std::ranges::dangling> = false; template <template <typename...> class T, typename ... Ts> inline constexpr bool not_dangling<T<Ts...>> = (not_dangling<Ts> && ...); Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 144/186
  138. Ban std::ranges::dangling template <typename <typename T> T> template conceptconstexpr not_dangling_type

    = not_dangling<T>; inline bool not_dangling = true; struct composable_function { template <typename Self, typename ... Ts> template <> constexpr decltype(auto) operator()(this Self&& self, inline constexpr bool Ts&& ... ts) not_dangling<std::ranges::dangling> = false; requires requires {{ template std::forward_like<Self>(self.f)( <template <typename...> class T, typename ... Ts> std::forward<Ts>(ts)...) inline constexpr bool } -> not_dangling_type;} not_dangling<T<Ts...>> = (not_dangling<Ts> && ...); ... Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 145/186
  139. Ban std::ranges::dangling template <typename <typename T> T> template conceptconstexpr not_dangling_type

    = not_dangling<T>; inline bool not_dangling = true; struct composable_function { template <typename Self, typename Ts> Only allow the call if the... wrapped template <> function does not return ‘dangling’. Self&& self, constexpr decltype(auto) operator()(this inline constexpr bool Ts&& ... ts) not_dangling<std::ranges::dangling> = false; requires requires {{ template std::forward_like<Self>(self.f)( <template <typename...> class T, typename ... Ts> std::forward<Ts>(ts)...) inline constexpr bool } -> not_dangling_type;} not_dangling<T<Ts...>> = (not_dangling<Ts> && ...); ... Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 146/186
  140. Ban std::ranges::dangling template <typename <typename T> T> template conceptconstexpr not_dangling_type

    = not_dangling<T>; inline bool not_dangling = true; struct composable_function { template <typename Self, typename Ts> Only allow the call if the... wrapped template <> function does not return ‘dangling’. Self&& self, constexpr decltype(auto) operator()(this inline constexpr bool Ts&& ... ts) not_dangling<std::ranges::dangling> = false; requires requires {{ template std::forward_like<Self>(self.f)( <template <typename...> class T, typename ... Ts> std::forward<Ts>(ts)...) inline constexpr bool } -> not_dangling_type;} not_dangling<T<Ts...>> = (not_dangling<Ts> && ...); ... https://godbolt.org/z/GozK4aE11 Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 147/186
  141. Ban std::ranges::dangling template <typename <typename T> T> template conceptconstexpr not_dangling_type

    = not_dangling<T>; inline bool Error info is decent, not_dangling = true; but not great struct composable_function { template <typename Self, typename ... Ts> template <> constexpr decltype(auto) operator()(this Self&& self, inline constexpr bool Ts&& ... ts) not_dangling<std::ranges::dangling> = false; requires requires {{ template std::forward_like<Self>(self.f)( <template <typename...> class T, typename ... Ts> std::forward<Ts>(ts)...) inline constexpr bool } -> not_dangling_type;} not_dangling<T<Ts...>> = (not_dangling<Ts> && ...); ... Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 148/186
  142. Ban std::ranges::dangling template <typename <typename T> T> template conceptconstexpr not_dangling_type

    = not_dangling<T>; inline bool Error info is decent, not_dangling = true; but not great struct composable_function Functions can be{ =delete(“message”) template <typename Self, typename ... Ts> template <> constexpr decltype(auto) operator()(this Self&& self, inline constexpr bool We can use that Ts&& ... ts) not_dangling<std::ranges::dangling> = false; requires requires {{ template std::forward_like<Self>(self.f)( <template <typename...> class T, typename ... Ts> std::forward<Ts>(ts)...) inline constexpr bool } -> not_dangling_type;} not_dangling<T<Ts...>> = (not_dangling<Ts> && ...); ... Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 149/186
  143. Ban std::ranges::dangling template <typename <typename T> T> template requires not_dangling<std::remove_cvref_t<T>>

    inline constexpr bool auto not_dangling_type(T&&) -> T; not_dangling = true; void not_dangling_type(auto&&) template <> = delete("return value would refer to dangling data"); inline constexpr bool not_dangling<std::ranges::dangling> = false; template <typename Self, typename ... Ts> constexpr<template auto operator()(this Ts&& ... template <typename...>Self&& class self, T, typename ...ts) Ts> -> decltype(not_dangling_type( inline constexpr bool std::forward_like<Self>(self.f) not_dangling<T<Ts...>> = (not_dangling<Ts> && ...); (std::forward<Ts>(ts)…))) { Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 150/186
  144. Ban std::ranges::dangling template <typename <typename T> T> template requires not_dangling<std::remove_cvref_t<T>>

    inline constexpr bool auto not_dangling_type(T&&) -> T; not_dangling = true; void not_dangling_type(auto&&) template <> Matches anything that doesn’t = delete("return value would refer to dangling data"); dangle, and gites the type back inline constexpr bool not_dangling<std::ranges::dangling> = false; template <typename Self, typename ... Ts> constexpr<template auto operator()(this Ts&& ... template <typename...>Self&& class self, T, typename ...ts) Ts> -> decltype(not_dangling_type( inline constexpr bool std::forward_like<Self>(self.f) not_dangling<T<Ts...>> = (not_dangling<Ts> && ...); (std::forward<Ts>(ts)…))) { Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 151/186
  145. Ban std::ranges::dangling template <typename <typename T> T> template requires not_dangling<std::remove_cvref_t<T>>

    inline constexpr bool auto not_dangling_type(T&&) -> T; not_dangling = true; void not_dangling_type(auto&&) template <> = delete("return value would refer to dangling data"); inline constexpr bool not_dangling<std::ranges::dangling> = false; template <typename Self, typename ... Ts> Matches anything dangling things, constexpr auto operator()(this Self&& self, Ts&& ... ts) template <template <typename...> class T, typename ... Ts> with a message -> decltype(not_dangling_type( inline constexpr bool std::forward_like<Self>(self.f) not_dangling<T<Ts...>> = (not_dangling<Ts> && ...); (std::forward<Ts>(ts)…))) { Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 152/186
  146. Ban std::ranges::dangling template <typename <typename T> T> template requires not_dangling<std::remove_cvref_t<T>>

    inline constexpr bool auto not_dangling_type(T&&) -> T; Return type of the wrapped not_dangling = true; function, or fails to compile, void with a not_dangling_type(auto&&) message. template <> = delete("return value would refer to dangling data"); inline constexpr bool not_dangling<std::ranges::dangling> = false; template <typename Self, typename ... Ts> constexpr<template auto operator()(this Ts&& ... template <typename...>Self&& class self, T, typename ...ts) Ts> -> decltype(not_dangling_type( inline constexpr bool std::forward_like<Self>(self.f) not_dangling<T<Ts...>> = (not_dangling<Ts> && ...); (std::forward<Ts>(ts)…))) { Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 153/186
  147. Ban std::ranges::dangling template <typename <typename T> T> template requires not_dangling<std::remove_cvref_t<T>>

    inline constexpr bool auto not_dangling_type(T&&) -> T; Return type of the wrapped not_dangling = true; function, or fails to compile, void with a not_dangling_type(auto&&) message. template <> = delete("return value would refer to dangling data"); inline constexpr bool not_dangling<std::ranges::dangling> = false; template <typename Self, typename ... Ts> constexpr<template auto operator()(this Ts&& ... template <typename...>Self&& class self, T, typename ...ts) Ts> -> decltype(not_dangling_type( inline constexpr bool std::forward_like<Self>(self.f) not_dangling<T<Ts...>> = (not_dangling<Ts> && ...); (std::forward<Ts>(ts)…))) { https://godbolt.org/z/4K6x4xrnf Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 154/186
  148. Transitive [[nodiscard]] If you return a value from a [[nodiscard]]

    function, that is not discarding the value, even if caller ignores the result. Let’s add an optional transitive [[nodiscard]] to composable_function<F> Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 155/186
  149. Transitive [[nodiscard]] template <typename F> struct nodiscard : F {};

    Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 156/186
  150. Transitive [[nodiscard]] template <typename F> struct nodiscard : F {};

    Let’s create a simple wrapper for functions that are to have the [[nodiscard]] attribute Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 157/186
  151. Transitive [[nodiscard]] template <typename F> struct nodiscard : F {};

    template <typename F> concept nodiscard_function = requires { []<typename T>(nodiscard<T>*){}( std::declval<std::remove_cvref_t<F>*>() ); }; Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 158/186
  152. Transitive [[nodiscard]] template <typename F> struct nodiscard : F {};

    And a concept for it, similar to the concept for composable_function<F> template <typename F> concept nodiscard_function = requires { []<typename T>(nodiscard<T>*){}( std::declval<std::remove_cvref_t<F>*>() ); }; Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 159/186
  153. Transitive [[nodiscard]] template <typename F> struct composable_function { F f;

    template <typename Self, typename ... Ts> constexpr auto operator()(this Self&& self, Ts&&...ts) -> decltype(std::forward_like<Self>(self.f) (std::forward<Ts>(ts)...)) ... template <typename Self, typename ... Ts> [[nodiscard]] constexpr auto operator()(this Self&& self, Ts&&...ts) -> decltype(std::forward_like<Self>(self.f) (std::forward<Ts>(ts)...)) requires nodiscard_function<F> ... Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 160/186
  154. Transitive [[nodiscard]] template <typename F> struct composable_function { Keep the

    operator() F f; that we had before template <typename Self, typename ... Ts> constexpr auto operator()(this Self&& self, Ts&&...ts) -> decltype(std::forward_like<Self>(self.f) (std::forward<Ts>(ts)...)) ... template <typename Self, typename ... Ts> [[nodiscard]] constexpr auto operator()(this Self&& self, Ts&&...ts) -> decltype(std::forward_like<Self>(self.f) (std::forward<Ts>(ts)...)) requires nodiscard_function<F> ... Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 161/186
  155. Transitive [[nodiscard]] template <typename F> struct composable_function { F f;

    template <typename Self, typename ... Ts> constexpr auto operator()(this Self&& self, Ts&&...ts) And create a new -> decltype(std::forward_like<Self>(self.f) one that is identical (std::forward<Ts>(ts)...)) ... template <typename Self, typename ... Ts> [[nodiscard]] constexpr auto operator()(this Self&& self, Ts&&...ts) -> decltype(std::forward_like<Self>(self.f) (std::forward<Ts>(ts)...)) requires nodiscard_function<F> ... Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 162/186
  156. Transitive [[nodiscard]] template <typename F> struct composable_function { F f;

    template <typename Self, typename ... Ts> constexpr auto operator()(this Self&& self, Ts&&...ts) -> decltype(std::forward_like<Self>(self.f) Except that it is has the [[nodiscard]] attribute (std::forward<Ts>(ts)...)) ... template <typename Self, typename ... Ts> [[nodiscard]] constexpr auto operator()(this Self&& self, Ts&&...ts) -> decltype(std::forward_like<Self>(self.f) (std::forward<Ts>(ts)...)) requires nodiscard_function<F> ... Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 163/186
  157. Transitive [[nodiscard]] template <typename F> struct composable_function { F f;

    template <typename Self, typename ... Ts> constexpr auto operator()(this Self&& self, Ts&&...ts) -> decltype(std::forward_like<Self>(self.f) Except that it is has the [[nodiscard]] attribute (std::forward<Ts>(ts)...)) ... template <typename Self, typename ... Ts> [[nodiscard]] If the function F is a constexpr auto operator()(thisnodiscard_function. Self&& self, Ts&&...ts) -> decltype(std::forward_like<Self>(self.f) (std::forward<Ts>(ts)...)) requires nodiscard_function<F> ... Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 164/186
  158. Transitive [[nodiscard]] template <typename F> struct composable_function { F f;

    FunctionsSelf, that returns new template <typename typename ... Ts> functions, partial constexpr auto operator()(this Self&& self, Ts&&...ts) application, pipes and -> decltype(std::forward_like<Self>(self.f) transform_args, needs to respect nodiscard. (std::forward<Ts>(ts)...)) ... template <typename Self, typename ... Ts> [[nodiscard]] constexpr auto operator()(this Self&& self, Ts&&...ts) -> decltype(std::forward_like<Self>(self.f) (std::forward<Ts>(ts)...)) requires nodiscard_function<F> ... Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 165/186
  159. Transitive [[nodiscard]] template <typename ... Ts> constexpr auto operator()(Ts&& ...

    ts) const requires (! requires{ this->f(std::forward<Ts>(ts)...);}){ auto bound = std::bind_back(this->f, std::forward<Ts>(ts)...); if constexpr (nodiscard_function<F>) { using RT=back_binding<nodiscard<decltype(bound)>>; return RT{std::move(bound)}; } else { using RT=back_binding<decltype(bound)>; return RT{std::move(bound)}; } } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 166/186
  160. Transitive [[nodiscard]] template <typename ... Ts> constexpr auto operator()(Ts&& ...If

    the ts) const function F is a requires (! requires{ this->f(std::forward<Ts>(ts)...);}){ nodiscard_function. auto bound = std::bind_back(this->f, std::forward<Ts>(ts)...); if constexpr (nodiscard_function<F>) { using RT=back_binding<nodiscard<decltype(bound)>>; return RT{std::move(bound)}; } else { using RT=back_binding<decltype(bound)>; return RT{std::move(bound)}; } } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 167/186
  161. Transitive [[nodiscard]] template <typename ... Ts> constexpr auto operator()(Ts&& ...

    ts) const requires (! requires{ this->f(std::forward<Ts>(ts)...);}){ auto bound = std::bind_back(this->f, std::forward<Ts>(ts)...); if constexpr (nodiscard_function<F>) { using RT=back_binding<nodiscard<decltype(bound)>>; return RT{std::move(bound)}; } else We return a nodiscard { version of the partial binding using RT=back_binding<decltype(bound)>; return RT{std::move(bound)}; } } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 168/186
  162. Transitive [[nodiscard]] template <typename ... Ts> constexpr auto operator()(Ts&& ...

    ts) const requires (! requires{ this->f(std::forward<Ts>(ts)...);}){ auto bound = std::bind_back(this->f, std::forward<Ts>(ts)...); if constexpr (nodiscard_function<F>) { using RT=back_binding<nodiscard<decltype(bound)>>; Otherwise the return RT{std::move(bound)}; same partial binding as before } else { using RT=back_binding<decltype(bound)>; return RT{std::move(bound)}; } } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 169/186
  163. Transitive [[nodiscard]] constexpr auto operator|(LH&& lh, RH&& rh) { auto

    pipe = ... using LHU = std::remove_cvref_t<LH>; if constexpr (nodiscard_function<decltype(rh.f)>) { using RT = rebind_template_t<LHU, decltype(nodiscard{pipe})>; return RT{std::move(pipe)}; } else { using RT = rebind_template_t<LHU, decltype(pipe)>; return RT{std::move(pipe)}; } } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 170/186
  164. Transitive [[nodiscard]] constexpr auto operator|(LH&& lh, RH&& rh) { auto

    pipe = ... using LHU = std::remove_cvref_t<LH>; if constexpr (nodiscard_function<decltype(rh.f)>) { using RT = rebind_template_t<LHU, decltype(nodiscard{pipe})>; return RT{std::move(pipe)}; If the function on the right } hand side of the pipe is a nodiscard_function else { using RT = rebind_template_t<LHU, decltype(pipe)>; return RT{std::move(pipe)}; } } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 171/186
  165. Transitive [[nodiscard]] Then the return type is the RH&& rh)

    { constexpr auto operator|(LH&& lh, same kind as the LH, auto pipe = ... rebound with a nodiscard using LHU = std::remove_cvref_t<LH>; version of the pipe if constexpr (nodiscard_function<decltype(rh.f)>) { using RT = rebind_template_t<LHU, decltype(nodiscard{pipe})>; return RT{std::move(pipe)}; } else { using RT = rebind_template_t<LHU, decltype(pipe)>; return RT{std::move(pipe)}; } } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 172/186
  166. Transitive [[nodiscard]] constexpr auto operator|(LH&& lh, RH&& rh) { auto

    pipe = ... using LHU = std::remove_cvref_t<LH>; if constexpr (nodiscard_function<decltype(rh.f)>) { using RT = rebind_template_t<LHU, decltype(nodiscard{pipe})>; Otherwise return RT{std::move(pipe)}; rebind as before } else { using RT = rebind_template_t<LHU, decltype(pipe)>; return RT{std::move(pipe)}; } } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 173/186
  167. Transitive [[nodiscard]] constexpr auto operator|(LH&& lh, RH&& rh) { auto

    pipe = ... using LHU = std::remove_cvref_t<LH>; if constexpr (nodiscard_function<decltype(rh.f)>) { using RT = rebind_template_t<LHU, decltype(nodiscard{pipe})>; Otherwise return RT{std::move(pipe)}; rebind as before } else { using RT = rebind_template_t<LHU, decltype(pipe)>; return RT{std::move(pipe)}; } https://godbolt.org/z/Ms6vh8bbx } Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 174/186
  168. Observations • Functional composition is powerful – It can be

    abused to the detriment of readability Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 177/186
  169. Observations • Functional composition is powerful – It can be

    abused to the detriment of readability – It can be used to improve expressive power and readability Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 178/186
  170. Observations • • Functional composition is powerful – It can

    be abused to the detriment of readability – It can be used to improve expressive power and readability Error messages are horrendous Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 179/186
  171. Observations • • Functional composition is powerful – It can

    be abused to the detriment of readability – It can be used to improve expressive power and readability • Compilers are awesome! Error messages are horrendous Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 180/186
  172. Observations • • Functional composition is powerful – It can

    be abused to the detriment of readability – It can be used to improve expressive power and readability • • Compilers are awesome! The code size is surprisingly small Error messages are horrendous Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 181/186
  173. Observations • Functional composition is powerful – – • It

    can be abused to the detriment of readability It can be used to improve expressive power and readability • • • Compilers are awesome! The code size is surprisingly small It is not always obvious that a function should be back_binding or front_binding Error messages are horrendous Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 182/186
  174. Observations • Functional composition is powerful – – • It

    can be abused to the detriment of readability It can be used to improve expressive power and readability Error messages are horrendous • • • Compilers are awesome! The code size is surprisingly small It is not always obvious that a function should be back_binding or front_binding – Named arguments could solve this Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 183/186
  175. Observations • Functional composition is powerful – – • It

    can be abused to the detriment of readability • Compilers are awesome! • The code size is surprisingly small • It is not always obvious that a function should be back_binding or front_binding – Named arguments could solve this • Reflection does not seem to offer help It can be used to improve expressive power and readability Error messages are horrendous Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 184/186
  176. Creating Composable Callables in Contemporary C++ [email protected] @rollbear.bsky.social @[email protected] @rollbear

    Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 185/186
  177. Creating Composable Callables in Contemporary C++ Furthermore - I think

    it is important that you understand your code [email protected] @rollbear.bsky.social @[email protected] @rollbear Creating Composable Callables in Contemporary C++ NDC{TechTown} 2026 © Björn Fahller @[email protected] 186/186