具有条件 return 的可调用对象的通用装饰器

generic decorators for callable objects with conditional return

我想为可调用对象编写装饰函数。

这是我现在拥有的:

#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));
}

此代码在 decorated 对象的 return 类型不是 void 时有效。否则错误:

<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. decorated 对象处理 void return 类型的简单且优化友好的方法是什么?
  2. 如何选择性地忽略不需要的 lambda 参数?使用可变 lambda 是一种方式,但它在用户端强制执行。
  3. 我应该在函数声明中对 return 使用 decltype(auto) 吗?

根据 Goswin 的建议,我提出了一个更短更清晰的版本:

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){

                const auto &callOnExit = [&](){
                    callAfter(std::as_const(args)...); // TODO: ignore parameters?
                };

                struct on_exit {
                    decltype(callOnExit) onExit;
                    ~on_exit() { !bool(std::uncaught_exceptions()) ? onExit() : void();}
                } onExit{callOnExit};

                return decorated(std::forward<decltype(args)>(args)...);
    };
}

可变参数捕获只能从 C++20 获得(这个 很有用)。然而,由于 callOnExit 不会比 (如果我错了请纠正我)包装 lambda 的参数,通过引用捕获所有参数是安全的。 https://godbolt.org/z/dEq19EjWo


一种天真的方法是用 if constexpr

检查 return 类型
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?
            }
        };
    }

我正在使用 Andrei Alexandrescus 谈论声明式控制流的 SCOPE_EXIT 宏。这里的技巧是 SCOPE_EXIT 创建一个带有 lambda 的对象(以下块)并在析构函数中执行 lambda。这会延迟执行,直到控制流退出块。

SCOPE_EXIT会一直执行代码,SCOPE_SUCCESS只有在没有抛出异常时才会执行代码,SCOPE_FAIL只有在抛出异常时才会执行代码。

注意:原来的装饰器在抛出异常时并没有执行callAfter

#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;
    };
}