可组合C ++函数装饰器

时间:2016-03-24 17:34:54

标签: c++ c++14 composition generic-programming python-decorators

Python具有function decorators非常有用的功能,此外,它还允许合成。例如,如果编写一个函数foo,那么您可以声明您希望foomemoized,而且如果是retried则还要this question缓存未命中foo也引发异常,通过:

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

装饰器可组合性允许独立开发像foo和各个函数装饰器这样的函数,然后根据需要混合它们。如果我们也可以在C ++中这样做(尽可能),那就太好了。

虽然StackOverflow上有关于函数装饰器的几个问题,但由于对装饰函数签名的严格假设,它们似乎都生成了不可组合的问题。例如,考虑cannot be applied to the result of itself的最佳投票回答。装饰的形式为

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

因此,它Here(对于特定装饰者使用memoization而言,这并不是一个问题)。

那么我们如何编写可组合函数装饰器呢?

2 个答案:

答案 0 :(得分:4)

创建可组合函数装饰器的另一种方法是使用一组 mixin 类。
它遵循一个最小的工作示例:

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

我不确定你提到的实际解决的问题是什么,但随时可以要求更改,如果可能的话我会尝试更新。

答案 1 :(得分: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_foofoo_decorator都由class Fn参数化,在这一点上几乎可以是任何东西。因此,例如,他们可以同时使用lambda函数或函子。所采用的参数(和返回类型)是(编译时)延迟到调用,其中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是它们组合的构建。

这种方法的一个缺点是装饰器具有依赖于功能的签名的状态的情况,例如,原始的记忆情况。使用类型擦除可以处理这个问题(参见构建{{3}}),但这有许多缺点,其中一个缺点是概念上可能在编译时捕获的错误,现在是在运行时捕获(当类型擦除检测到非法使用时)。