Reputation: 35485
I am trying to figure out how to decorate a std::function
with "before" and "after" hooks.
I have some trouble figuring out the right syntax. This is what I have so far:
// create a "before" hook
template<typename Functor, typename Hook>
Functor hook_before(const Functor & original, Hook hook)
{
// not legal, but illustrates what I want to achieve
template<typename Args ...args>
return [=](Args ...args)
{
hook();
original(args...);
};
}
My sample application is on Ideone.
Can anyone help me figure it out?
Upvotes: 2
Views: 1785
Reputation: 2454
Try the following.
template<typename Functor, typename Hook>
struct BeforeHooked
{
Functor f;
Hook hook;
template<class... Args>
typename std::result_of<F(Args&&...)>::type
operator()(Args&&... args)
{
hook();
return f(std::forward<Args&&>(args)...);
}
};
template<typename Functor, typename Hook>
Functor hook_before(Functor f, Hook hook)
{
return BeforeHooked{f, hook};
}
The code is untested, but assuming you have a compiler which can compile it, I think it should do what you want. Unlike the other answers, it can accept any functor, not just std::function
, and if you give it a polymorphic functor it remains polymorphic.
Upvotes: 2
Reputation: 3509
You can do it like so:
#include <functional>
#include <iostream>
template <class Hook, class ReturnType, class... ArgType>
std::function<ReturnType(ArgType...)> hook_before(
const std::function<ReturnType(ArgType...)>& original,
Hook hook)
{
return [=](ArgType... args){
hook();
return original(std::move(args)...);
};
}
int main()
{
std::function<int(int, int)> sum = [](int a, int b) { return a + b; };
std::cout << sum(3, 4) << std::endl;
auto myhook = []() { std::cout << "Calculating sum" << std::endl; };
auto hooked_sum = hook_before(sum, myhook);
std::cout << hooked_sum(3, 4) << std::endl;
}
The hook_before
function accepts two functors, and returns another that accepts the same arguments as the first (the ArgType parameter pack), but calls hook
first.
Upvotes: 3
Reputation: 279295
Here goes (untested):
template <typename HOOK, typename RET, typename... ARGS>
struct BeforeHook {
std::function<RET(ARGS...)> functor;
HOOK hook;
BeforeHook(blah) : blah {};
RET operator()(ARGS&&... args) const {
hook();
return functor(args...);
}
};
template <typename HOOK, typename RET, typename... ARGS>
BeforeHook<HOOK, RET, ARGS...> hook_before(const std::function<RET(ARGS...)> &original, HOOK hook) {
return BeforeHook<HOOK, RET, ARGS...>(original, hook);
}
Usage:
auto hooked = hook_before(original_functor, hook_functor);
hooked(args_for_original_functor); // calls hook_functor, then original_functor
Or something along those lines. The original_functor needs to be convertible to std::function
, but pretty much everything callable is. Both functors need to be cost-callable, but you could remove the const
from operator()
if you like.
If you want to experiment with returning a lambda rather than an instance of BeforeHook
, use the same trick with the template arguments RET
and ...ARGS
, and find out whether it's possible to use a template argument pack in a lambda:
template <typename HOOK, typename RET, typename... ARGS>
std::function<RET(ARGS...)> hook_before(const std::function<RET(ARGS...)> &original, HOOK hook) {
return [=](ARGS&&... args) -> RET {
hook();
return original(args...);
};
}
Either way, I think the key trick is using std::function
in a template argument deduction to separate the return type from the arguments.
Upvotes: 3