2

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?

StackedCrooked
  • 34,653
  • 44
  • 154
  • 278

3 Answers3

3

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.

Steve Jessop
  • 273,490
  • 39
  • 460
  • 699
  • That would need an implicit conversion to a parameter which is being used for template parameter deduction, so it won't work (unless you explicitly specify all the template parameters - but I assume that's not what you had in mind). – ymett Jun 06 '12 at 10:00
  • @ymett: On the one hand, the questioner says "how to decorate a std::function with "before" and "after" hooks", so no conversion necessary, the input is a `function` already. On the other hand, I claimed it would work with a conversion, which is a bit feeble if the conversion has to be explicit. – Steve Jessop Jun 06 '12 at 10:55
3

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.

Node
  • 3,443
  • 16
  • 18
2

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.

ymett
  • 2,425
  • 14
  • 22