C 17中的Python风格装饰器

Python Style Decorator in C++17

本文关键字:风格 Python 中的      更新时间:2023-10-16

我正在使用最新的C 技术制作python的旅程。我已经在这里看到了一些解决方案(类似Python的C 装饰器),但是我想知道它是否可以做得更好。在其他人的帮助下(构建std ::函数参数来自lambda),我提出了以下解决方案。

template<typename TWrapped>
auto DurationAssertDecorator(const std::chrono::high_resolution_clock::duration& maxDuration, TWrapped&& wrapped)
{
    return [wrapped = std::forward<TWrapped>(wrapped), maxDuration](auto&&... args)
    {
        const auto startTimePoint = std::chrono::high_resolution_clock::now();
        static_assert(std::is_invocable<TWrapped, decltype(args)...>::value, "Wrapped object must be invocable");
        if constexpr (!(std::is_void<decltype(wrapped(std::forward<decltype(args)>(args)...))>::value))
        {
            // return by reference will be here not converted to return by value?
            //auto result = wrapped(std::forward<decltype(args)>(args)...);
            decltype(wrapped(std::forward<decltype(args)>(args)...)) result = wrapped(std::forward<decltype(args)>(args)...);
            const auto endTimePoint = std::chrono::high_resolution_clock::now();
            const auto callDuration = endTimePoint - startTimePoint;
            assert(callDuration <= maxDuration);
            return result;
        }
        else
        {
            wrapped(std::forward<decltype(args)>(args)...);
            const auto endTimePoint = std::chrono::high_resolution_clock::now();
            const auto callDuration = endTimePoint - startTimePoint;
            assert(callDuration <= maxDuration);
        }
    };
}

我不是故意使用下面的"自动"来确保返回类型是我期望的(或至少兼容)。

我将能够将其与任何可召唤:无状态lambda,statefull lambda,struct functor,function指针,std :: function

std::function<double(double)> decorated = DurationAssertDecorator(1s, [](const double temperature) { return temperature + 5.0; });
double a = decorated (4);

构图也可以:

std::function<double()> wrapped = LogDecorator(logger, [] { return 4.0; });
std::function<double()> wrapped_wrapped = DurationAssertDecorator(1s, functor);
std::function<void(double)> decorated = DurationAssertDecorator(1s, 5);

到目前为止,它可以解决问题,但是:

  • 情况 - 包装函数具有返回值 - 我不确定我是否只是通过自动获得结果,并且包装的返回值是参考。如果是这样,则将进行副本,而是保留参考(按指针返回,值得还可以)。这就是为什么我想出那个奇怪的结构。我可以做得更好吗?
  • 还有哪些其他改进/修复?

我已经意识到,如果我使用raii对象进行预呼叫活动和后呼叫活动,我可以更简化代码。无需再进行空隙和非空隙返回值处理。

template<typename TWrapped>
auto DurationAssertDecorator(const std::chrono::high_resolution_clock::duration& maxDuration, TWrapped&& wrapped)
{
    return [wrapped = std::forward<TWrapped>(wrapped), maxDuration](auto&&... args) mutable
    {
        static_assert(std::is_invocable<TWrapped, decltype(args)...>::value, "Wrapped object must be invocable");
        struct Aspect
        {
            // Precall logic goes into the constructor
            Aspect(const std::chrono::high_resolution_clock::duration& maxDuration)
                : _startTimePoint(std::chrono::high_resolution_clock::now())
                , _maxDuration(maxDuration)
            {}
            // Postcall logic goes into the destructor
            ~Aspect()
            {
                const auto endTimePoint = std::chrono::high_resolution_clock::now();
                const auto callDuration = endTimePoint - _startTimePoint;
                assert(callDuration <= _maxDuration);
            }
            const std::chrono::high_resolution_clock::time_point _startTimePoint;
            const std::chrono::high_resolution_clock::duration& _maxDuration;
        } aspect(maxDuration);
        return wrapped(std::forward<decltype(args)>(args)...);
    };
}

它与普通用例一起使用:

auto wrappedFunctor = DurationAssertDecorator(1s, [](const double temperature)  { return temperature; });

我也想与非const函子一起使用,例如可变的lambdas:

auto wrappedFunctor = DurationAssertDecorator(1s, 
    [firstCall = true](const double temperature) mutable
    {
        if (firstCall)
        {
            firstCall = false;
            return temperature;
        }
        std::this_thread::sleep_for(2s);
        return temperature;
    });

所以我对此解决方案感到非常满意。