Home > Mobile >  generic decorators for callable objects with conditional return
generic decorators for callable objects with conditional return

Time:05-15

I want to write decorator functions for callable objects.

This is what I have now:

#include <utility>

template <typename DecoratedT, typename CallableT>
constexpr auto before_callable(DecoratedT &&decorated, CallableT &&callBefore)
{
    return [decorated = std::forward<DecoratedT>(decorated),
            callBefore = std::forward<CallableT>(callBefore)](auto &&...args){

                callBefore(std::as_const(args)...); // TODO: ignore parameters?
                auto &&res = decorated(std::forward<decltype(args)>(args)...);
                return res;
                };
}

template <typename DecoratedT, typename CallableT>
constexpr auto after_callable(DecoratedT &&decorated, CallableT &&callAfter)
{
    return [decorated = std::forward<DecoratedT>(decorated),
            callAfter = std::forward<CallableT>(callAfter)](auto &&...args){
                auto &&res = decorated(std::forward<decltype(args)>(args)...);
                callAfter(std::as_const(args)...); // TODO: ignore parameters?

                return res;
    };
}

template <typename DecoratedT, typename CallBeforeT, typename CallAfterT>
constexpr auto decorate_callable(DecoratedT &&decorated, 
                                CallBeforeT &&callBefore, 
                                CallAfterT &&callAfter)
{
    return before_callable(after_callable(std::forward<DecoratedT>(decorated), 
                                          std::forward<CallAfterT>(callAfter)),
                            std::forward<CallBeforeT>(callBefore));
}

This code works while a decorated object has a return type which is not void. Error otherwise:

<source>:21:24: error: forming reference to void
   21 |                 auto &&res = decorated(std::forward<decltype(args)>(args)...);
      |                        ^~~
#include <iostream>

template <typename SumT>
void print(const SumT& sum)
{
    const auto &res = sum(4, 811);
    std::cout << res << std::endl;
}

int main()
{
    struct {
        int operator()(int lhs, int rhs) const
        {
            std::cout << "summing\n"; 
            return lhs   rhs;
        }
    } sum{};

    const auto &printBefore = [](const int lhs, const int rhs, const auto &...){
        std::cout << "Before sum (args): " << lhs << " " << rhs << std::endl;
    };

    const auto &printAfter = [](const auto &...){
        std::cout << "After sum" << std::endl;
    };

    std::cout << "Undecorated: ";
    print(sum);
    std::cout << "\nDecorated Before:\n";
    print(before_callable(sum, printBefore));

    std::cout << "\nDecorated After:\n";
    print(after_callable(sum, printAfter));

    std::cout << "\nDecorated Before and After:\n";
    print(decorate_callable(sum, printBefore, printAfter));

    struct {
        void operator()() const {}
    } retVoid{};

    const auto &voidDecorated = decorate_callable(retVoid, 
                                    [](const auto &...){}, 
                                    [](const auto &...){});
    // voidDecorated(); // does not compile

    return 0;
}

https://godbolt.org/z/x94ehTq17

  1. What is a simple and optimization-friendly way of handling void return type from a decorated object?
  2. How can I optionally ignore arguments for lambdas where I don't need them? Using variadic lambdas is a one way, but it is enforced on the user side.
  3. Should I use decltype(auto) for return in function declarations?

CodePudding user response:

I'm using the SCOPE_EXIT macro from Andrei Alexandrescus talk about Declarative Control Flow. The trick here is that SCOPE_EXIT creates an object with a lambda (the following block) and executes the lambda in the destructor. This delays the execution until the control flow exits the block.

SCOPE_EXIT will always execute the code, SCOPE_SUCCESS will only execute the code if no exception is thrown and SCOPE_FAIL will execute the code only when an exception is thrown.

Note: the original decorator didn't execute the callAfter when an exception is thrown.

#include <utility>
#include <exception>

#ifndef CONCATENATE_IMPL
#define CONCATENATE_IMPL(s1, s2) s1##s2
#endif
#ifndef CONCATENATE
#define CONCATENATE(s1, s2) CONCATENATE_IMPL(s1, s2)
#endif

#ifndef ANONYMOUS_VARIABLE
#ifdef __COUNTER__
#define ANONYMOUS_VARIABLE(str) CONCATENATE(str, __COUNTER__)
#else
#define ANONYMOUS_VARIABLE(str) CONCATENATE(str, __LINE__)
#endif
#endif

template <typename FunctionType>
class ScopeGuard {
  FunctionType function_;
public:
  explicit ScopeGuard(const FunctionType& fn) : function_(fn) { }
  explicit ScopeGuard(const FunctionType&& fn) : function_(std::move(fn)) { }
  ~ScopeGuard() noexcept {
    function_();
  }
};

enum class ScopeGuardOnExit { };

template <typename Fun>
ScopeGuard<Fun> operator  (ScopeGuardOnExit, Fun&& fn) {
  return ScopeGuard<Fun>(std::forward<Fun>(fn));
}
#define SCOPE_EXIT \
  auto ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE) \
  = ScopeGuardOnExit()   [&]()

template <typename DecoratedT, typename CallableT>
constexpr auto after_callable(DecoratedT &&decorated, CallableT &&callAfter)
{
    return [decorated = std::forward<DecoratedT>(decorated),
            callAfter = std::forward<CallableT>(callAfter)](auto &&...args){
                SCOPE_EXIT {
                    callAfter(std::as_const(args)...); // TODO: ignore parameters?
                };
                auto &&res = decorated(std::forward<decltype(args)>(args)...);

                return res;
    };
}

CodePudding user response:

A naive way is to check return type with if constexpr

template<typename DecoratedT, typename CallableT>
    constexpr decltype(auto) before_callable(DecoratedT &&decorated, CallableT &&callBefore)
    {
        return [decorated = std::forward<DecoratedT>(decorated),
                callBefore = std::forward<CallableT>(callBefore)](auto &&...args) {
            callBefore(std::as_const(args)...);   // TODO: ignore parameters?
            return decorated(std::forward<decltype(args)>(args)...);
        };
    }

    template<typename DecoratedT, typename CallableT>
    constexpr decltype(auto) after_callable(DecoratedT &&decorated, CallableT &&callAfter)
    {
        return [decorated = std::forward<DecoratedT>(decorated),
                callAfter = std::forward<CallableT>(callAfter)](auto &&...args) {
            constexpr auto ret_type_is_void =
                std::is_void_v<decltype(decorated(std::forward<decltype(args)>(args)...))>;

            if constexpr (!ret_type_is_void)
            {
                auto &&res = decorated(std::forward<decltype(args)>(args)...);
                callAfter(std::as_const(args)...);   // TODO: ignore parameters?
                return res;
            }
            else
            {
                decorated(std::forward<decltype(args)>(args)...);
                callAfter(std::as_const(args)...);   // TODO: ignore parameters?
            }
        };
    }
  • Related