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?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With