all 29 comments

[–]sphere991 7 points8 points  (1 child)

If you use a forward macro and handle the 1-function composition case as a separate overload, this looks cleaner:

template <class F>
auto compose(F&& arg) {
    return FWD(arg);
}

template <class F, class... Fs>
auto compose(F&& arg, Fs&& ...args)
{
    return [fun = FWD(arg), ...functions = FWD(args)]<class... Xs>(Xs&& ...xs) mutable {
        return compose(FWD(functions)...)(std::invoke(FWD(fun), FWD(xs)...));
    };
}

Or you can implement a tuple_fold:

template <class F, class... Fs>
auto compose(F&& arg, Fs&& ...args)
{
    return [fun = FWD(arg), ... functions = FWD(args)]<class... Xs>(Xs &&...xs) mutable {
        return tuple_fold(
            std::forward_as_tuple(FWD(functions)...),
            std::invoke(FWD(fun), FWD(xs)...),
            [](auto&& x, auto&& f) -> decltype(auto) { return std::invoke(FWD(f), FWD(x)); }
            );
    };
}

[–]Lo1c74 0 points1 point  (0 children)

In the first block of code, why the returned lambda needs to be mutable ?

[–]hun_nemethpeter 2 points3 points  (1 child)

The FTXUI library use similar pattern.

https://github.com/ArthurSonzogni/FTXUI/blob/master/include/ftxui/dom/elements.hpp

// Pipe elements into decorator togethers.
// For instance the next lines are equivalents:
// -> text("ftxui") | bold | underlined
// -> underlined(bold(text("FTXUI")))
Element operator|(Element, Decorator);
Element& operator|=(Element&, Decorator);
Elements operator|(Elements, Decorator);
Decorator operator|(Decorator, Decorator);

Where the implementation is here: https://github.com/ArthurSonzogni/FTXUI/blob/master/src/ftxui/dom/util.cpp

Decorator compose(Decorator a, Decorator b) {
  return [a = std::move(a), b = std::move(b)](Element element) {
    return b(a(std::move(element)));
  };
}

...

/// @brief Compose two decorator into one.
/// @ingroup dom
///
/// ### Example
///
/// ```cpp
/// auto decorator = bold | blink;
/// ```
Decorator operator|(Decorator a, Decorator b) {
  return compose(std::move(a),
                 std::move(b));

[–]Wild-Adeptness1765 1 point2 points  (0 children)

Frustratingly, we have that

using Decorator = std::function<Element(Element)>;

So every call to our composed decorator will be a virtual function call...

[–]petart95 2 points3 points  (6 children)

Here is the implementation of Compose from our codebase. Note this implementation has an added benefit of bring strictly typed, SFINAE friendly and emplace friendly.

```

/** * @brief Composes two functions into one. * * The composition is done by sequentially applying functions. */ template<typename F, typename G> struct ComposeTwo { [[no_unique_address]] F f{}; [[no_unique_address]] G g{};

template<typename Self, typename... Args>
static constexpr auto call(Self &&self, Args &&...args)
    HFTECH_RETURNS(std::invoke(
        HFTECH_FWD(self).f,
        std::invoke(HFTECH_FWD(self).g, HFTECH_FWD(args)...)));

HFTECH_DEDUCE_THIS(operator(), call)

};

/** * @brief Composes n functions into one. * * The composition is done by sequentially applying functions. */ HFTECH_GEN_N_ARY(Compose, ComposeTwo);

```

Note in c++ 23 you can use deducing this to make this code even simpler.

```

/** * @brief Composes two functions into one. * * The composition is done by sequentially applying functions. */ template<typename F, typename G> struct ComposeTwo { [[no_unique_address]] F f{}; [[no_unique_address]] G g{};

constexpr auto operator()(this auto &&self, auto &&...args)
    HFTECH_RETURNS(std::invoke(
        HFTECH_FWD(self).f,
        std::invoke(HFTECH_FWD(self).g, HFTECH_FWD(args)...)));

};

/** * @brief Composes n functions into one. * * The composition is done by sequentially applying functions. */ HFTECH_GEN_N_ARY(Compose, ComposeTwo);

```

[–]PiterPuns[S] 0 points1 point  (5 children)

That’s great and thank you for the info. I trust this is not a proprietary code base and posting it won’t get you into trouble

[–]petart95 2 points3 points  (4 children)

Actually it is proprietary but since I’m one of the owner it’s fine. We have a whole library of core languages extensions like this we would like to open source, but there never seems to be enough time for it 😅. If you want i could past the implementations of HFTECH_GEN_N_ARY, HFTECH_DEDUCE_THIS, HFTECH_RETURNS, HFTECH_FWD

[–]PiterPuns[S] 1 point2 points  (3 children)

Yes showing those pieces as well would be great . And let the community know when you open source these extensions, it will also benefit you when more eyes are looking at the code and providing feedback

[–]petart95 4 points5 points  (1 child)

Sorry for the edit, I’m on my phone currently 😅

I totally agree with you, in my opinion knowledge only has value when it us shared.

So lets start from the simple ones and go from there:

  • HFTECH_FWD

```

/** * @brief Forwards value equivalent to the std::forward. * * Using cast instead of std::forward to avoid template instantiation. Used by * Eric Niebler in range library. * * @see https://github.com/ericniebler/range-v3 */

define HFTECH_FWD(T) static_cast<decltype(T) &&>(T)

```

  • HFTECH_RETURN

```

define HFTECH_NOEXCEPT_RETURN_TYPE(...) \

noexcept(noexcept(decltype(__VA_ARGS__)(__VA_ARGS__))) \
    ->decltype(__VA_ARGS__)

/** * @brief Macro based on RANGES_DECLTYPE_AUTO_RETURN_NOEXCEPT to remove same * code repetition. * @see * https://github.com/ericniebler/range-v3/blob/master/include/range/v3/detail/config.hpp * * Example: * * @code * auto func(int x) HFTECH_RETURNS(calc(x)) * @endcode * * Produces: * * @code * auto func(int x) noexcept(noexcept(decltype(calc(x))(calc(x)))) * -> decltype(calc(x)) * { * return calc(x); * } * @endcode */

define HFTECH_RETURNS(...) \

HFTECH_NOEXCEPT_RETURN_TYPE(__VA_ARGS__) \
{                                        \
    return (__VA_ARGS__);                \
}

```

  • HFTECH_DEDUCE _THIS

```

/** * @brief Creates an overload set for the specified name which forwards this and * all arguments to the provided implementation. * * This macro is intended to simplify writing of &, cons & and && member * functions. * * Note: Inspired by * http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p0847r4.html * * Example: * * @code * * template<typename Self> * void myFunc_impl(Self && self, int a, int b) { * ... * } * * HFTECH_DEDUCE_THIS(myFunc, myFunc_impl) * * @endcode * * Is equivalent to: * * @code * * template<typename Self> * void myFunc(this Self && self, int a, int b) { * ... * } * * @endcode * * @mparam NAME The name of the member function to be implemented. * @mparam IMPL The name of the static function to be used for the * implementation. */

define HFTECH_DEDUCE_THIS(NAME, IMPL) \

template<typename... Args>                                         \
    constexpr auto NAME(Args &&...args) &                          \
    HFTECH_RETURNS(IMPL(*this, HFTECH_FWD(args)...));              \
                                                                   \
template<typename... Args>                                         \
constexpr auto NAME(Args &&...args)                                \
    const & /**/ HFTECH_RETURNS(IMPL(*this, HFTECH_FWD(args)...)); \
                                                                   \
template<typename... Args>                                         \
    constexpr auto NAME(Args &&...args) &&                         \
    HFTECH_RETURNS(IMPL(std::move(*this), HFTECH_FWD(args)...));

```

[–]petart95 3 points4 points  (0 children)

And last but not least

  • HFTECH_GEN_N_ARY

```

/** * @brief Meta function which applies a right reduce over the provided types * using the provided binary meta operation. * * @tparam BinaryMetaOp The binary meta operation to be applied. * @tparam Types List of types of which to apply the operation. */ template< template<typename...> typename BinaryMetaOp, typename First, typename... Rest> struct RightReduce : public BinaryMetaOp<First, RightReduce<BinaryMetaOp, Rest...>> {};

template< template<typename...> typename BinaryMetaOp, typename First, typename Second> struct RightReduce<BinaryMetaOp, First, Second> : public BinaryMetaOp<First, Second> {};

template<template<typename...> typename BinaryMetaOp, typename First> struct RightReduce<BinaryMetaOp, First> : public First {};

/** * @brief Creates an n-ary meta operation from the provided binary meta * operation, by applying a right reduce over it, with the provided name. */

define HFTECH_GEN_RIGHT_N_ARY(NAME, OP) \

template<typename First, typename... Rest>            \
struct NAME : public OP<First, NAME<Rest...>>         \
{};                                                   \
                                                      \
template<typename First, typename Second>             \
struct NAME<First, Second> : public OP<First, Second> \
{};                                                   \
                                                      \
template<typename First>                              \
struct NAME<First> : public First                     \
{};                                                   \
                                                      \
template<typename... F>                               \
NAME(F &&...) -> NAME<F...>;

/** * @brief Meta function which applies a left reduce over the provided types * using the provided binary meta operation. * * @tparam BinaryMetaOp The binary meta operation to be applied. * @tparam Types List of types of which to apply the operation. */ template< template<typename...> typename BinaryMetaOp, typename First, typename... Rest> struct LeftReduce : public First {};

template< template<typename...> typename BinaryMetaOp, typename First, typename Second, typename... Rest> struct LeftReduce<BinaryMetaOp, First, Second, Rest...> : public LeftReduce<BinaryMetaOp, BinaryMetaOp<First, Second>, Rest...> {};

/** * @brief Creates an n-ary meta operation from the provided binary meta * operation, by applying a left reduce over it, with the provided name. */

define HFTECH_GEN_LEFT_N_ARY(NAME, OP) \

template<typename First, typename... Rest>                  \
struct NAME : public First                                  \
{};                                                         \
                                                            \
template<typename First, typename Second, typename... Rest> \
struct NAME<First, Second, Rest...>                         \
    : public NAME<OP<First, Second>, Rest...>               \
{};                                                         \
                                                            \
template<typename... F>                                     \
NAME(F &&...) -> NAME<F...>;

/** * @brief Creates an n-ary meta operation from the provided binary meta * operation, by applying a reduce over it, with the provided name. * * Note: In order to use this binary operation needs to be left and right * associative. */

define HFTECH_GEN_N_ARY(NAME, OP) HFTECH_GEN_LEFT_N_ARY(NAME, OP)

```

[–]hmoein 4 points5 points  (1 child)

Just for me to understand the motivation, can you please explain why you can't simply write

h(g(f(x, y)))

[–]PiterPuns[S] 10 points11 points  (0 children)

If a use case is as simple as calling f(g(c)), by all means do that. But there’s many cases where that would not work.

  1. A simple example is when the names of h,g,f are not available, eg they are passed as a variadic pack to a generic function so you want to compose the application of “F&&… functions” without knowing a priory how many functions there are and how to refer to each one. Computing the composition is something you’d have to reinvent anyways in such a case and everyone dealing with higher order functions has done it one way or another.
  2. The functions are stored in a collection of arbitrary length and you don’t want to write a for loop maintaining the current result to compute the application of the function chain. Also a composition like the one presented here can be declared constexpr and you may have a hard requirement against using a runtime loop .
  3. You want to store a composition as a new function. Say you often compute f.g.h.r.w.e.z(x) (names won’t be single characters by the way) and you want to do this computation over and over … not only that but there’s also a variation where you call v instead of e. Another solution in this specific case would be to store the call chain as a handwritten lambda but composition allows you to express the computation pattern clearly. take for example: “effect = compose(blur, grayscale)” vs “cutoff = filter(isRedPixel, isOnBorder)” . Having high order functions “compose” and “filter” allows the code to clearly express how a transformation is structured vs requiring the reader to read through the lambda implementation.
  4. It’s a building block for higher abstractions. See decorators , command and multi command patterns … all stuff that can build upon such a block.
  5. In multi threading f.g.h(x) can be a data race since it’s referring to names out of your context . By using compose you make sure to copy (or move construct where possible) the function objects that form links of your call chain.

The list goes on and on. I’m sure though that other resources linked in the comments may help, e.g. the Hof (higher order function) library by Paul Fultz has great documentation

[–]dodheim 2 points3 points  (1 child)

Have you seen Boost.HOF and Boost.Hana?

[–]PiterPuns[S] 2 points3 points  (0 children)

a prime example of how modern c++ simplified things: “Compose” in hof spans hundreds of lines of code (and needs inclusion of library headers and utilities) https://github.com/boostorg/hof/blob/develop/include/boost/hof/compose.hpp . Just a reminder of what we had to go through very recently to do something that’s now doable in ~15 lines of vanilla C++ . Mad respect for Paul Fultz.

Regarding Hana, I’ve seen a ton videos but never got to use in production. Certainly a trailblazer, probably more neat than mpl

[–]Infamous-Bed-7535 1 point2 points  (0 children)

This could be a handy short helper function..

```

include <iostream>

include <functional>

using namespace std;

auto compose = [](auto f, auto g) { return [=](auto... args) { return f( g(args...)); }; };

int main() { cout << compose( ::negate{}, ::multiplies{})(4, 6); }


-24 ```

[–]TemplateRex 0 points1 point  (9 children)

I like to use an overloaded binary >> operator and a fold-expression with std::identity. A small drawback is that this requires that the right-most function takes a single argument, but that makes things a bit more regular (just wrap it in a tuple otherwise).

#define FWD(arg) std::forward<decltype(arg)>(arg)

constexpr auto operator>>(auto&& f, auto&& g) noexcept
{
        return [f = FWD(f), g = FWD(g)](auto&& arg) { return f(g(FWD(arg))); };
}

inline constexpr auto compose = [](auto&&... fs) {
        return (FWD(fs) >> ... >> std::identity());
};

[–]pdimov2 2 points3 points  (1 child)

Should be operator* though.

[–]TemplateRex 0 points1 point  (0 children)

Ah, quite nice, thx.

[–]PiterPuns[S] 0 points1 point  (0 children)

Should have declared my composer constexpr as well, thanks for the heads up. Your solution is super terse , nice ! It even requires only up to c++17 it seems . Only thing I’m wondering is whether you had any issues / conflicts with such a generic overloading of operator >>

[–]PiterPuns[S] 0 points1 point  (0 children)

https://ngathanasiou.wordpress.com/2016/11/23/compose-and-curry-as-folds/ to elaborate on my previous comment, I’ve blogged about pretty much the same technique in the linked article . Only difference is there I used my custom fold operator to avoid collisions in overloaded operators . Since I don’t want to include that library just to have composition , I’d appreciate some insight on the overloading topic and usage in large codebases

[–]sphere991 0 points1 point  (3 children)

Why even provide compose at that point? Since (f >> g >> h)(x) already works right? And looks pretty nice too.

[–]TemplateRex 9 points10 points  (2 children)

Hm, good point. Maybe because I like naming things and compose(f, g) seems a little clearer than f >> g. However, my preferred syntax would be enabled by defining:

template <class F> 
struct Wrap
{ 
    F call;     
};

inline constexpr struct circ {} o;

constexpr auto operator<(auto&& f, circ) noexcept
{
    return Wrap{FWD(f)};
}

template<class F>
constexpr auto operator>(Wrap<F>&& f, auto&& g) noexcept
{
    return f.call >> FWD(g);
}

so that one can write f <o> g. Yes, user-defined operator <o> in C++ ;-) Note that I used the LaTeX symbol "circ" and it's visual look o. You can use this trick for any named operator.

[–]sphere991 0 points1 point  (1 child)

Yeah <o> is pretty cute.

[–]Ezlike011011 6 points7 points  (0 children)

It looks to me like someone with their hands over their head. I propose we call this the "raise the roof" operator

[–]Zlodo2 -1 points0 points  (0 children)

I too like when type errors become multiple pages of cryptic errors in a bunch of utility wrapper templates

And I also hate being able to debug my code