Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Composable C++ Function Decorators

Python has a very useful feature of function decorators, which, moreover, allows composition. For example, if write a function foo, then you can state that you would like foo to be memoized, but also retried more than a single time in case of a cache miss in which foo also raises an exception, by:

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

Decorator composability allows developing functions like foo and individual function decorators independently, and then mixing them as needed. It would be nice if we could do so in C++ as well (to the extent possible).

While there are several questions on StackOverflow regarding function decorators, they all seem to generate non-composable ones, due to rigid assumptions on the signature of the decorated function. E.g., consider the excellent top-voted answer to this question. The decoration is of the form

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

Consequently, it cannot be applied to the result of itself (admittedly not much of an issue for the specific decorator use of memoization).

How can we write composable function decorators, then?

like image 258
Ami Tavory Avatar asked Mar 24 '16 17:03

Ami Tavory


1 Answers

Another way to create composable function decorators is by using a set of mixin classes.
It follows a minimal, working example:

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

I'm not sure what of the problems you mentioned it actually solves, but feel free to ask for changes and I'll try to update it if possible.

like image 171
skypjack Avatar answered Oct 19 '22 01:10

skypjack