Tutorial: closure and adaptor

| Home | Libraries | People | FAQ | More

## Tutorial: `closure` and `adaptor`

Here is `boost::stl_interfaces::closure`:

        /** An invocable consisting of a contained invocable `f`.  Calling
            `operator()` with some argument `t` calls `f(t)` and returns the
            result.  This type is typically used to capture a the result of a call
            to `bind_back()`. */
        template<typename F>
        struct closure : range_adaptor_closure<closure<F>>
        {
            constexpr closure(F f) : f_(std::move(f)) {}

    #if BOOST_STL_INTERFACES_USE_CONCEPTS
            template<typename T>
            requires std::invocable<F const &, T>
    #else
            template<
                typename T,
                typename Enable =
                    std::enable_if_t<detail::is_invocable_v<F const &, T>>>
    #endif
            constexpr decltype(auto) operator()(T && t) const &
            {
                return f_((T &&) t);
            }

    #if BOOST_STL_INTERFACES_USE_CONCEPTS
            template<typename T>
            requires std::invocable<F &&, T>
    #else
            template<
                typename T,
                typename Enable = std::enable_if_t<detail::is_invocable_v<F &&, T>>>
    #endif
            constexpr decltype(auto) operator()(T && t) &&
            {
                return std::move(f_)((T &&) t);
            }

        private:
            [[no_unique_address]] F f_;
        };

As you can see, it inherits from `boost::stl_interfaces::range_adaptor_closure`, and its only member function is a call operator that forwards to the function `F` it is constructed from. It exists simply to adapt some function that constructs a particular view to use in pipe expressions.

`adaptor` is slightly different; it operates essentially just like `closure` if you call its `operator()` with all arguments necessary to invoke its function `F` — it just constructs a view. However, if you pass it all the arguments but the first, it returns a `closure` with all those arguments bound into it:

        /** Adapts an invocable `f` as a view adaptor.  Calling
            `operator(args...)` will either: call `f(args...)` and return the
            result, if `f(args...)` is well-formed; or return
            `closure(stl_interfaces::bind_back(f, args...))` otherwise. */
        template<typename F>
        struct adaptor
        {
            constexpr adaptor(F f) : f_(std::move(f)) {}

            // clang-format off
            template<typename... Args>
            constexpr auto operator()(Args &&... args) const
            // clang-format on
            {
    #if BOOST_STL_INTERFACES_USE_CONCEPTS
                if constexpr (std::is_invocable_v<F const &, Args...>) {
                    return f_((Args &&) args...);
                } else {
                    return closure(
                        stl_interfaces::bind_back(f_, (Args &&) args...));
                }
    #else
                return detail::adaptor_impl<
                    F const &,
                    detail::is_invocable_v<F const &, Args...>,
                    Args...>::call(f_, (Args &&) args...);
    #endif
            }

        private:
            [[no_unique_address]] F f_;
        };

This allows you to use it both as a view-constructor (e.g. `take(range, 3)`), and as a closure-constructor (e.g. `range | take(3)`).

| Important
Due to the way `adaptor` works, your views's constructors should always take their view parameter before any other parameters. Otherwise, the combination of `bind_back()` and `closure` will probably not do anything very useful.