Python Style Decorator в C ++ 17 - PullRequest
       12

Python Style Decorator в C ++ 17

0 голосов
/ 14 февраля 2019

Я нахожусь в процессе создания декоратора, похожего на питона, с использованием новейших доступных технологий C ++.Я уже видел какое-то решение ( Python-подобные C ++ декораторы ), но мне интересно, можно ли сделать это лучше.С помощью других ( Построение аргумента std :: function из 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);
        }
    };
}

Я не использую ниже "auto", чтобы убедиться, чточто возвращаемый тип - это то, что я ожидаю (или, по крайней мере, совместим).

Я буду в состоянии использовать его с любым вызываемым: лямбда без сохранения состояния, лямбда с полным состоянием, функция структуры, указатель на функцию, 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);

Это не должно быть в порядке - int литерал 5 не может быть вызван:

std::function<void(double)> decorated = DurationAssertDecorator(1s, 5);

Пока он делаетуловка, однако:

  • Случай, когда у обернутой функции есть возвращаемое значение - я не был уверен, получу ли я просто результат автоматически, а возвращаемое значение у обернутой является ссылкой.Если так, то вместо этого будет иметь место копия с сохранением ссылки (возврат по указателю и по значению должен быть в порядке).Вот почему я придумал эту странную конструкцию.Могу ли я сделать это лучше?
  • Какие еще улучшения / исправления возможны?

1 Ответ

0 голосов
/ 15 февраля 2019

Я понял, что могу значительно упростить код, если использую объект RAII для действий до и после вызова.Обработка возвращаемых значений void и non-void больше не требуется.

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

Я также хотел работать с не-константные функторы, такие как непостоянные лямбды:

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

Так что я вполне доволен этим решением.

...