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

Creating Composable Callables in Contemporary C++

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

June 18, 2026

More Decks by Björn Fahller

Other Decks in Programming

Transcript

  1. Björn Fahller Creating Composable Callables in Contemporary C++ or How

    I nerd sniped myself with a lightning talk https://www.youtube.com/watch?v=XKVyoWvPCCw
  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. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

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

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

    © Björn Fahller @[email protected] 13/176 A small example bool all_are_small(std::span<const int> numbers) { return std::ranges::all_of(numbers, [](int x){return x < 10;}); } Let’s make a function for this comparison
  7. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 14/176 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;}); }
  8. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 15/176 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;}); }
  9. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 16/176 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;}); } Return a lambda which binds the argument used on the right hand side
  10. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 17/176 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)); }
  11. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 18/176 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/EPd97Kq9P
  12. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 19/176 A first generalization 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)); } This has a name in the standard library. It is std::less<>
  13. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 20/176 A first generalization 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)); } template <typename F> constexpr auto back_binding(F f = {}) { return [f](auto rh) { return [f,rh](auto lh) { return f(lh, rh); }; }; }
  14. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 21/176 A first generalization 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)); } template <typename F> constexpr auto back_binding(F f = {}) { return [f](auto rh) { return [f,rh](auto lh) { return f(lh, rh); }; }; } A function to call
  15. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 22/176 A first generalization 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)); } template <typename F> constexpr auto back_binding(F f = {}) { return [f](auto rh) { return [f,rh](auto lh) { return f(lh, rh); }; }; }
  16. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 23/176 A first generalization 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)); } template <typename F> constexpr auto back_binding(F f = {}) { return [f](auto rh) { return [f,rh](auto lh) { return f(lh, rh); }; }; } Call function with the right hand argument bound
  17. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 24/176 A first generalization 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)); } template <typename F> constexpr auto back_binding(F f = {}) { return [f](auto rh) { return [f,rh](auto lh) { return f(lh, rh); }; }; } Call function with the right hand argument bound Looks unnecessarily complicated
  18. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 25/176 A first generalization 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)); } template <typename F> constexpr auto back_binding(F f = {}) { return [f](auto rh) { return std::bind_back(f,rh); }; }
  19. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 26/176 A first generalization 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)); } template <typename F> constexpr auto back_binding(F f = {}) { return [f](auto rh) { return std::bind_back(f,rh); }; }
  20. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 27/176 A first generalization 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)); } template <typename F> constexpr auto back_binding(F f = {}) { return [f](auto rh) { return std::bind_back(f,rh); }; } https://en.cppreference.com/cpp/utility/functional/bind_back
  21. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 28/176 A first generalization 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)); } template <typename F> constexpr auto back_binding(F f = {}) { return [f](auto rh) { return std::bind_back(f,rh); }; } Better!
  22. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 29/176 A first generalization 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)); } template <typename F> constexpr auto back_binding(F f = {}) { return [f](auto rh) { return std::bind_back(f,rh); }; } constexpr auto less_than = back_binding<std::less<>>(); Better!
  23. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 30/176 A first generalization 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)); } template <typename F> constexpr auto back_binding(F f = {}) { return [f](auto rh) { return std::bind_back(f,rh); }; } constexpr auto less_than = back_binding<std::less<>>(); Better! https://godbolt.org/z/v97P76T11
  24. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 31/176 pipes &S::num | less_than(10)
  25. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 32/176 pipes &S::num | less_than(10) I want to be able to write this
  26. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 33/176 pipes auto operator|(FL lh, FR rh)
  27. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 34/176 pipes auto operator|(FL lh, FR rh) Need a way to identify the types as composable functions
  28. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 35/176 pipes auto operator|(FL lh, FR rh) Need a way to identify the types as composable functions The lambdas will not suffice
  29. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 36/176 pipes template <typename F> struct composable_function { F f; };
  30. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 37/176 pipes template <typename F> struct composable_function { F f; }; The function to call
  31. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 38/176 pipes template <typename F> struct composable_function { F f; template <typename ... Ts> decltype(auto) operator()(Ts&& ... ts) const { return f(std::forward<Ts>(ts)...); } };
  32. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 39/176 pipes template <typename F> struct composable_function { F f; template <typename ... Ts> decltype(auto) operator()(Ts&& ... ts) const { return f(std::forward<Ts>(ts)...); } }; When called with arguments
  33. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

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

    © Björn Fahller @[email protected] 41/176 pipes 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)...); } }; But only if the function can be called
  35. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 42/176 pipes 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)...); } }; template <typename F> struct back_binding : composable_function<F> { };
  36. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 43/176 pipes 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)...); } }; template <typename F> struct back_binding : composable_function<F> { }; back_binding is a composable_function
  37. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 44/176 pipes 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)...); } }; template <typename F> struct back_binding : composable_function<F> { using composable_function<F>::operator(); template <typename ... Ts> auto operator()(Ts&& ... ts) const { } };
  38. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 45/176 pipes 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)...); } }; template <typename F> struct back_binding : composable_function<F> { using composable_function<F>::operator(); template <typename ... Ts> auto operator()(Ts&& ... ts) const { } }; It overloads the call operator
  39. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 46/176 pipes 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)...); } }; template <typename F> struct back_binding : composable_function<F> { using composable_function<F>::operator(); template <typename ... Ts> auto operator()(Ts&& ... ts) const { auto bound = std::bind_back( this->f, std::forward<Ts>(ts)...); using RT = back_binding<decltype(bound)>; return RT{std::move(bound)}; } };
  40. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 47/176 pipes 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)...); } }; template <typename F> struct back_binding : composable_function<F> { using composable_function<F>::operator(); template <typename ... Ts> auto operator()(Ts&& ... ts) const { auto bound = std::bind_back( this->f, std::forward<Ts>(ts)...); using RT = back_binding<decltype(bound)>; return RT{std::move(bound)}; } }; Bind the arguments at the back
  41. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 48/176 pipes 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)...); } }; template <typename F> struct back_binding : composable_function<F> { using composable_function<F>::operator(); template <typename ... Ts> auto operator()(Ts&& ... ts) const { auto bound = std::bind_back( this->f, std::forward<Ts>(ts)...); using RT = back_binding<decltype(bound)>; return RT{std::move(bound)}; } }; Return another back_binding
  42. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 49/176 pipes template <typename F> struct composable_function { ... }; template <typename F> struct back_binding : composable_function<F> { ... }; constexpr auto less_than = back_binding<std::less<>>();
  43. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

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

    © Björn Fahller @[email protected] 51/176 pipes 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/d89fqjsqn Usage is the same as before
  45. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 52/176 pipes template <typename F> struct composable_function; template <typename F> concept composable_function_type = requires { };
  46. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 53/176 pipes template <typename F> struct composable_function; template <typename F> concept composable_function_type = requires { }; Create a concept that can be used by the pipe operator
  47. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 54/176 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>*>() ); };
  48. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 55/176 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>*>() ); }; Require that a pointer to type F
  49. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 56/176 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>*>() ); }; Can be passed to a function accepting a pointer to a composable_function
  50. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 57/176 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>*>() ); }; This matches back_binding too since it models an is-A relationship through inheritance
  51. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 58/176 pipes template < composable_function_type LH, composable_function_type RH> auto operator|(LH&& lh, RH&& rh) { }
  52. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 59/176 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.
  53. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 60/176 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)}; }
  54. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 61/176 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)}; } Capture the functions
  55. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 62/176 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)}; } Call the left function with the arguments to the pipe
  56. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 63/176 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)}; } Call the right function with the result of the left
  57. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 64/176 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)}; } But what should the return type be?
  58. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 65/176 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)}; } But what should the return type be? The pipe is called the same way as the left function, so it must be of the same kind.
  59. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 66/176 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)}; } LH is a specialization of a composable function type. C<F> We need the same kind of template, but instantiated with our pipe.
  60. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 67/176 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)}; } LH is a specialization of a composable function type. C<F> We need the same kind of template, but instantiated with our pipe. template <typename> struct rebind_template;
  61. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 68/176 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)}; } LH is a specialization of a composable function type. C<F> We need the same kind of template, but instantiated with our pipe. template <typename> struct rebind_template; template <template <typename> class T, typename Original> struct rebind_template<T<Original>> { };
  62. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 69/176 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)}; } LH is a specialization of a composable function type. C<F> We need the same kind of template, but instantiated with our pipe. template <typename> struct rebind_template; template <template <typename> class T, typename Original> struct rebind_template<T<Original>> { }; When instantiated with a template T, instantiated with type Original, i.e. T<Original>
  63. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 70/176 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)}; } LH is a specialization of a composable function type. C<F> We need the same kind of template, but instantiated with our pipe. template <typename> struct rebind_template; template <template <typename> class T, typename Original> struct rebind_template<T<Original>> { template <typename Substitute> using with = T<Substitute>; };
  64. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 71/176 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)}; } LH is a specialization of a composable function type. C<F> We need the same kind of template, but instantiated with our pipe. template <typename> struct rebind_template; template <template <typename> class T, typename Original> struct rebind_template<T<Original>> { template <typename Substitute> using with = T<Substitute>; }; We offer an alias which is the template T instantiated with a substitute
  65. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 72/176 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)}; } LH is a specialization of a composable function type. C<F> We need the same kind of template, but instantiated with our pipe. template <typename> struct rebind_template; template <template <typename> class T, typename Original> struct rebind_template<T<Original>> { template <typename Substitute> using with = T<Substitute>; }; template <typename T, typename S> using rebind_template_t = rebind_template<T>::template with<S>; And, finally, an alias for less verbose use
  66. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 73/176 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)}; }
  67. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 74/176 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)}; } struct S { int num; std::string_view name; }; constexpr auto get_num = composable_function{std::mem_fn(&S::num)}; bool all_are_small(std::span<const S> numbers) { return std::ranges::all_of(numbers, get_num | less_than(10)); }
  68. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 75/176 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)}; } struct S { int num; std::string_view name; }; constexpr auto get_num = composable_function{std::mem_fn(&S::num)}; bool all_are_small(std::span<const S> numbers) { return std::ranges::all_of(numbers, get_num | less_than(10)); } Composable function get_num, which when called with an S will return the member num
  69. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 76/176 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)}; } struct S { int num; std::string_view name; }; constexpr auto get_num = composable_function{std::mem_fn(&S::num)}; bool all_are_small(std::span<const S> numbers) { return std::ranges::all_of(numbers, get_num | less_than(10)); } And it can be used in a pipe
  70. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 77/176 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)}; } struct S { int num; std::string_view name; }; constexpr auto get_num = composable_function{std::mem_fn(&S::num)}; bool all_are_small(std::span<const S> numbers) { return std::ranges::all_of(numbers, get_num | less_than(10)); } https://godbolt.org/z/WKsdE6jaY
  71. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 78/176 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); } 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)}; } Two tiny convenience functions
  72. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 79/176 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); } 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)}; } struct S { int num; std::string_view name; }; bool all_are_small(std::span<const S> numbers) { return std::ranges::all_of(numbers, &S::num | less_than(10)); }
  73. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 80/176 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); } 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)}; } struct S { int num; std::string_view name; }; bool all_are_small(std::span<const S> numbers) { return std::ranges::all_of(numbers, &S::num | less_than(10)); } More convenient short hand
  74. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 81/176 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); } 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)}; } struct S { int num; std::string_view name; }; bool all_are_small(std::span<const S> numbers) { return std::ranges::all_of(numbers, &S::num | less_than(10)); }
  75. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 82/176 transform_args How do I, e.g., sort on a member?
  76. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 83/176 transform_args template <typename Proj, composable_function_type F> constexpr auto transform_args(Proj&& proj, F&& f) { }
  77. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 84/176 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
  78. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 85/176 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)}; }
  79. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 86/176 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)}; } Bind the projection and the function
  80. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 87/176 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)}; } Transform each argument through the projection in the call
  81. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 88/176 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)}; }
  82. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 89/176 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)}; } Same as pipe?
  83. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 90/176 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)}; } Same as pipe? 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)...)); };
  84. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 91/176 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)}; } 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)...)); }; The pipe passes all arguments to the left hand side function
  85. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 92/176 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)}; } 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)...)); }; transform_args calls the projection function on each argument
  86. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 93/176 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)); }
  87. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 94/176 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)); } Create a convenience wrapper for pointer to member
  88. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 95/176 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)); } But this overload set is not an object that can be passed
  89. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 96/176 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)); } }
  90. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 97/176 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)); } } Add another level of indirection
  91. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 98/176 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)); } } inline constexpr auto transform_args = front_binding { []<typename ... Ts>(Ts&& ... ts) -> decltype(impl::transform_args(std::forward<Ts>(ts)...)) { return impl::transform_args(std::forward<Ts>(ts)...); } };
  92. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 99/176 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)); } } inline constexpr auto transform_args = front_binding { []<typename ... Ts>(Ts&& ... ts) -> decltype(impl::transform_args(std::forward<Ts>(ts)...)) { return impl::transform_args(std::forward<Ts>(ts)...); } };
  93. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 100/176 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)); } } inline constexpr auto transform_args = front_binding { []<typename ... Ts>(Ts&& ... ts) -> decltype(impl::transform_args(std::forward<Ts>(ts)...)) { return impl::transform_args(std::forward<Ts>(ts)...); } };
  94. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 101/176 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)}; } };
  95. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 102/176 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)}; } }; Same as back_binding, but calls std::bind_front instead of std::bind_back
  96. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 103/176 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)}; } }; This disambiguation is useful also for back_binding
  97. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 104/176 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)); }
  98. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 105/176 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)); }
  99. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 106/176 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); }
  100. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 107/176 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); } back_binding wrapper for std::ranges::is_sorted
  101. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 108/176 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); } Sort on &S::num with order as argument
  102. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 109/176 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); } Sorted in increasing order using less_than
  103. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 110/176 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); }
  104. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 111/176 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/EvGe5xrYG
  105. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 112/176 perfect(er) forwarding What if I want to bind something that is not copyable?
  106. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 113/176 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); }
  107. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 114/176 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); } Outrageous compilation error message!
  108. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 115/176 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); } Outrageous compilation error message! clang: 97 lines
  109. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 116/176 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); } Outrageous compilation error message! clang: 97 lines gcc: 34 lines
  110. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 117/176 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); } Outrageous compilation error message! clang: 97 lines gcc: 34 lines MSVC: 11 lines
  111. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 118/176 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); } Outrageous compilation error message! clang: 97 lines gcc: 34 lines MSVC: 11 lines All function call operators are const, so it passes a const unique_ptr in the call.
  112. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 119/176 perfect(er) forwarding https://godbolt.org/z/1qGaEvjM6 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)...)) { return std::forward_like<Self>(self.f) (std::forward<Ts>(ts)...); } };
  113. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 120/176 perfect(er) forwarding https://godbolt.org/z/1qGaEvjM6 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)...)) { return std::forward_like<Self>(self.f) (std::forward<Ts>(ts)...); } }; C++20 “deducing this” or “explicit object parameter”
  114. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 121/176 perfect(er) forwarding https://godbolt.org/z/1qGaEvjM6 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)...)) { return std::forward_like<Self>(self.f) (std::forward<Ts>(ts)...); } }; Forwards f as: F&& or F& or const F&& or const F&
  115. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 122/176 perfect(er) forwarding https://godbolt.org/z/1qGaEvjM6 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)...)) { return std::forward_like<Self>(self.f) (std::forward<Ts>(ts)...); } }; Repeat for every operator() and every lambda
  116. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 123/176 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); }
  117. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 124/176 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); } Outrageous compilation error message! clang: 97 lines gcc: 34 lines MSVC: 14 lines
  118. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 125/176 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); } Only gcc give’s a reasonable hint, pointing to consume_ptr(3);
  119. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 126/176 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/s9qYPbWsc
  120. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 127/176 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/s9qYPbWsc
  121. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 128/176 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/s9qYPbWsc
  122. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 129/176 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.
  123. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 130/176 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()?
  124. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 131/176 template <typename T> inline constexpr bool not_dangling = true; template <> inline constexpr bool not_dangling<std::ranges::dangling> = false; Ban std::ranges::dangling
  125. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 132/176 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> && ...); Ban std::ranges::dangling
  126. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 133/176 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> && ...); std::ranges::mismatch() returns mismatch_result<borrowed_iterator_t<R1>, borrowed_iterator_t<R2>> Ban std::ranges::dangling
  127. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 134/176 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> && ...); Ban std::ranges::dangling We can now use a concept to constraing the calls
  128. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 135/176 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> && ...); Ban std::ranges::dangling template <typename T> concept not_dangling_type = not_dangling<T>; struct composable_function { template <typename Self, typename ... Ts> constexpr decltype(auto) operator()(this Self&& self, Ts&& ... ts) requires requires {{ std::forward_like<Self>(self.f)( std::forward<Ts>(ts)...) } -> not_dangling_type;} ...
  129. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 136/176 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> && ...); Ban std::ranges::dangling template <typename T> concept not_dangling_type = not_dangling<T>; struct composable_function { template <typename Self, typename ... Ts> constexpr decltype(auto) operator()(this Self&& self, Ts&& ... ts) requires requires {{ std::forward_like<Self>(self.f)( std::forward<Ts>(ts)...) } -> not_dangling_type;} ... Only allow the call if the wrapped function does not return ‘dangling’.
  130. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 137/176 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> && ...); Ban std::ranges::dangling https://godbolt.org/z/ab7bT1EhK template <typename T> concept not_dangling_type = not_dangling<T>; struct composable_function { template <typename Self, typename ... Ts> constexpr decltype(auto) operator()(this Self&& self, Ts&& ... ts) requires requires {{ std::forward_like<Self>(self.f)( std::forward<Ts>(ts)...) } -> not_dangling_type;} ... Only allow the call if the wrapped function does not return ‘dangling’.
  131. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 138/176 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> && ...); Ban std::ranges::dangling template <typename T> concept not_dangling_type = not_dangling<T>; struct composable_function { template <typename Self, typename ... Ts> constexpr decltype(auto) operator()(this Self&& self, Ts&& ... ts) requires requires {{ std::forward_like<Self>(self.f)( std::forward<Ts>(ts)...) } -> not_dangling_type;} ... Error info is decent, but not great
  132. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 139/176 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> && ...); Ban std::ranges::dangling template <typename T> concept not_dangling_type = not_dangling<T>; struct composable_function { template <typename Self, typename ... Ts> constexpr decltype(auto) operator()(this Self&& self, Ts&& ... ts) requires requires {{ std::forward_like<Self>(self.f)( std::forward<Ts>(ts)...) } -> not_dangling_type;} ... Error info is decent, but not great Functions can be =delete(“message”) We can use that
  133. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 140/176 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> && ...); Ban std::ranges::dangling template <typename T> requires not_dangling<std::remove_cvref_t<T>> auto not_dangling_type(T&&) -> T; void not_dangling_type(auto&&) = delete("return value would refer to dangling data"); template <typename Self, typename ... Ts> constexpr auto operator()(this Self&& self, Ts&& ... ts) -> decltype(not_dangling_type( std::forward_like<Self>(self.f) (std::forward<Ts>(ts)…))) {
  134. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 141/176 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> && ...); Ban std::ranges::dangling template <typename T> requires not_dangling<std::remove_cvref_t<T>> auto not_dangling_type(T&&) -> T; void not_dangling_type(auto&&) = delete("return value would refer to dangling data"); template <typename Self, typename ... Ts> constexpr auto operator()(this Self&& self, Ts&& ... ts) -> decltype(not_dangling_type( std::forward_like<Self>(self.f) (std::forward<Ts>(ts)…))) { Matches anything that doesn’t dangle, and gites the type back
  135. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 142/176 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> && ...); Ban std::ranges::dangling template <typename T> requires not_dangling<std::remove_cvref_t<T>> auto not_dangling_type(T&&) -> T; void not_dangling_type(auto&&) = delete("return value would refer to dangling data"); template <typename Self, typename ... Ts> constexpr auto operator()(this Self&& self, Ts&& ... ts) -> decltype(not_dangling_type( std::forward_like<Self>(self.f) (std::forward<Ts>(ts)…))) { Matches anything dangling things, with a message
  136. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 143/176 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> && ...); Ban std::ranges::dangling template <typename T> requires not_dangling<std::remove_cvref_t<T>> auto not_dangling_type(T&&) -> T; void not_dangling_type(auto&&) = delete("return value would refer to dangling data"); template <typename Self, typename ... Ts> constexpr auto operator()(this Self&& self, Ts&& ... ts) -> decltype(not_dangling_type( std::forward_like<Self>(self.f) (std::forward<Ts>(ts)…))) { Return type of the wrapped function, or fails to compile, with a message.
  137. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 144/176 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> && ...); Ban std::ranges::dangling template <typename T> requires not_dangling<std::remove_cvref_t<T>> auto not_dangling_type(T&&) -> T; void not_dangling_type(auto&&) = delete("return value would refer to dangling data"); template <typename Self, typename ... Ts> constexpr auto operator()(this Self&& self, Ts&& ... ts) -> decltype(not_dangling_type( std::forward_like<Self>(self.f) (std::forward<Ts>(ts)…))) { Return type of the wrapped function, or fails to compile, with a message. https://godbolt.org/z/r3dfzr6T1
  138. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 145/176 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>
  139. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 146/176 Transitive [[nodiscard]] template <typename F> struct nodiscard : F {};
  140. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

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

    © Björn Fahller @[email protected] 148/176 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>*>() ); };
  142. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 149/176 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>*>() ); }; And a concept for it, similar to the concept for composable_function<F>
  143. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 150/176 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> ...
  144. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 151/176 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> ... Keep the operator() that we had before
  145. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 152/176 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> ... And create a new one that is identical
  146. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 153/176 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> ... Except that it is has the [[nodiscard]] attribute
  147. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 154/176 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> ... Except that it is has the [[nodiscard]] attribute If the function F is a nodiscard_function.
  148. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 155/176 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> ... Functions that returns new functions, partial application, pipes and transform_args, needs to respect nodiscard.
  149. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 156/176 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)}; } }
  150. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 157/176 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)}; } } If the function F is a nodiscard_function.
  151. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 158/176 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)}; } } We return a nodiscard version of the partial binding
  152. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 159/176 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)}; } } Otherwise the same partial binding as before
  153. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 160/176 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)}; } }
  154. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 161/176 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)}; } } If the function on the right hand side of the pipe is a nodiscard_function
  155. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 162/176 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)}; } } Then the return type is the same kind as the LH, rebound with a nodiscard version of the pipe
  156. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 163/176 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)}; } } Otherwise rebind as before
  157. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 164/176 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)}; } } Otherwise rebind as before https://godbolt.org/z/hGsdxhqhb
  158. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 166/176 Observations • Functional composition is powerful
  159. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 167/176 Observations • Functional composition is powerful – It can be abused to the detriment of readability
  160. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 168/176 Observations • Functional composition is powerful – It can be abused to the detriment of readability – It can be used to improve expressive power and readability
  161. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 169/176 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
  162. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 170/176 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!
  163. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 171/176 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
  164. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 172/176 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
  165. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 173/176 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
  166. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 174/176 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 • Reflection can (probably) help
  167. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 175/176 [email protected] @rollbear @[email protected] Creating Composable Callables in Contemporary C++ @rollbear.bsky.social
  168. Creating Composable Callables in Contemporary C++ ACCU on Sea 2026

    © Björn Fahller @[email protected] 176/176 [email protected] @rollbear @[email protected] Creating Composable Callables in Contemporary C++ @rollbear.bsky.social Furthermore - I think it is important that you understand your code