3

Python имеет очень полезную функцию function decorators, что, кроме того, позволяет композицию. Например, если написать функцию foo, то вы можете указать, что вы хотели бы foo быть memoized, но и retried более чем один раз в случае промаха кэша, в котором foo также вызывает исключение, автор:Композитные декораторы функций C++

@lru_cache 
@retry 
def foo(...): 

Декоративная композиция позволяет создавать функции, такие как foo, и индивидуальные декораторы функций независимо, а затем смешивать их по мере необходимости. Было бы неплохо, если бы мы могли это сделать и на C++ (насколько это возможно).

Хотя в StackOverflow есть несколько вопросов относительно декораторов функций, все они, похоже, генерируют неконсолидируемые из-за жестких допущений на подпись декорированной функции. Например, рассмотрите отличный опрос, полученный сверху, до this question. Украшение формы

template <typename R, typename... Args> 
std::function<R (Args...)> memo(R (*fn)(Args...)) { 

Следовательно, cannot be applied to the result of itself (правда, не такая уж большая проблема для конкретного использования декоратора запоминанием).

Как мы можем написать декоративные декораторы функций?

+0

Пример, который вы даете, не очень убедителен; в этой реализации нет ничего, что требует, чтобы он использовал указатель на функцию. –

ответ

0

Один из способов создания декоративных декораторов - это облегчение предположения о подписи, сделанной декоратором. Скажем, у нас есть

template<class Fn> 
struct foo_decorator 
{ 
    template<typename ...Args> 
    auto operator()(Args &&...args) const -> 
     typename std::result_of<Fn(Args...)>::type; 
}; 

template<class Fn> 
foo_decorator<Fn> make_foo(const Fn &fn) {return foo_decorator<Fn>();} 

Как можно видеть, как make_foo и foo_decorator параметризованы class Fn, которые могут быть практически все, что в этой точке. Следовательно, они могут взять, например, как лямбда-функцию, так и функтор. Принятые аргументы (и возвращаемый тип) (время компиляции) откладываются до вызова, где выведенные параметры шаблона вызова функции C++ будут заполнять остальные детали по мере необходимости.

Используя это, вот простая регистрацию декоратор:

#include <type_traits> 
#include <cmath> 
#include <iostream>  

template<class Fn> 
struct logger 
{ 
    logger(const Fn &fn, const std::string &name) : m_fn(fn), m_name{name}{} 

    template<typename ...Args> 
    auto operator()(Args &&...args) const -> 
     typename std::result_of<Fn(Args...)>::type 
    { 
     std::cout << "entering " << m_name << std::endl; 
     const auto ret = m_fn(std::forward<Args>(args)...); 
     std::cout << "leaving " << m_name << std::endl; 
     return ret; 
    } 

private: 
    Fn m_fn; 
    std::string m_name; 
}; 

template<class Fn> 
logger<Fn> make_log(const Fn &fn, const std::string &name) 
{ 
    return logger<Fn>(fn, name); 
} 

int main() 
{ 
    auto fn = make_log([](double x){return std::sin(x);}, "sin"); 
    std::cout << fn(4.0) << std::endl; 
} 

Here является сборкой этого декоратора, here является сборкой из декоратора Повторной попытки, и here является сборкой композиции из них.

Один из недостатков этого подхода заключается в случаях, когда декоратор имеет состояние, основанное на сигнатуре функции, например, на первоначальном случае memoization. С этим можно справиться с использованием стирания типа (см. Сборку here), но это имеет ряд недостатков, одна из которых заключается в том, что ошибки, которые концептуально могли быть пойманы во время компиляции, теперь попадают во время выполнения (когда стирание типа обнаруживает незаконное использование).

3

Другой способ создания компонуемые функции декораторы является использование набора подмешать классов.
вытекает минимальный, рабочий пример:

#include<iostream> 
#include<functional> 
#include<utility> 
#include<type_traits> 

template<class T> 
struct LoggerDecoratorA: public T { 
    template<class U> 
    LoggerDecoratorA(const U &u): T{u} { } 

    template<typename... Args> 
    auto operator()(Args&&... args) const -> 
     typename std::enable_if< 
      not std::is_same< 
       typename std::result_of<T(Args...)>::type, 
       void 
      >::value, 
     typename std::result_of<T(Args...)>::type>::type 
    { 
     using namespace std; 
     cout << "> logger A" << endl; 
     auto ret = T::operator()(std::forward<Args>(args)...); 
     cout << "< logger A" << endl; 
     return ret; 
    } 

    template<typename... Args> 
    auto operator()(Args&&... args) const -> 
     typename std::enable_if< 
      std::is_same< 
       typename std::result_of<T(Args...)>::type, 
       void 
      >::value, 
     typename std::result_of<T(Args...)>::type>::type 
    { 
     using namespace std; 
     cout << "> logger A" << endl; 
     T::operator()(std::forward<Args>(args)...); 
     cout << "< logger A" << endl; 
    } 
}; 

template<class T> 
struct LoggerDecoratorB: public T { 
    template<class U> 
    LoggerDecoratorB(const U &u): T{u} { } 

    template<typename... Args> 
    auto operator()(Args&&... args) const -> 
     typename std::enable_if< 
      not std::is_same< 
       typename std::result_of<T(Args...)>::type, 
       void 
      >::value, 
     typename std::result_of<T(Args...)>::type>::type 
    { 
     using namespace std; 
     cout << "> logger B" << endl; 
     auto ret = T::operator()(std::forward<Args>(args)...); 
     cout << "< logger B" << endl; 
     return ret; 
    } 

    template<typename... Args> 
    auto operator()(Args&&... args) const -> 
     typename std::enable_if< 
      std::is_same< 
       typename std::result_of<T(Args...)>::type, 
       void 
      >::value, 
     typename std::result_of<T(Args...)>::type>::type 
    { 
     using namespace std; 
     cout << "> logger B" << endl; 
     T::operator()(std::forward<Args>(args)...); 
     cout << "< logger B" << endl; 
    } 
}; 

int main() { 
    std::function<int()> fn = [](){ 
     using namespace std; 
     cout << 42 << endl; 
     return 42; 
    }; 

    std::function<void()> vFn = [](){ 
     using namespace std; 
     cout << "void" << endl; 
    }; 

    using namespace std; 

    decltype(fn) aFn = 
     LoggerDecoratorA<decltype(fn)>(fn); 
    aFn(); 

    cout << "---" << endl; 

    decltype(vFn) bVFn = 
     LoggerDecoratorB<decltype(vFn)>(vFn); 
    bVFn(); 

    cout << "---" << endl; 

    decltype(fn) abFn = 
     LoggerDecoratorA<LoggerDecoratorB<decltype(fn)>>(fn); 
    abFn(); 

    cout << "---" << endl; 

    decltype(fn) baFn = 
     LoggerDecoratorB<LoggerDecoratorA<decltype(fn)>>(fn); 
    baFn(); 
} 

Я не уверен, что проблемы, которые вы упомянули, что на самом деле решает, но не стесняйтесь просить изменений и я буду стараться обновлять его, если это возможно.

+0

Спасибо за ответ! –

+0

Добро пожаловать, я надеюсь, что это соответствует вашим ожиданиям. Он отлично работает только с функциями, но это то, что вы искали, не так ли? – skypjack

+0

@AmyTavory обновлено немного sfinae – skypjack