Question

I have something like this:

typedef std::function<void(int param1, int param2)> TheCallback;

void callTheCallback(TheCallback& theCallback) {
    theCallback(1, 2);
}

int main(int argc, char *argv[]) {
    TheCallback cb = [](int a, int b) { 
        std::cout << "Param 1:" << a << ", Param 2:" << b << std::endl;
    };

    cb(100, 200);
    return 0;
}

My question is - what is the best way to hook std::function (or create a custom "backwards-compatible" std::function template) so that each time when such function is called it would also print some custom string (e.g. "Called") before doing anything else?

There is nice solution for C++11 that uses variadic templates. So is there anything similar possible for C++0x (e.g. Visual Studio 2010)?

Était-ce utile?

La solution 3

If you want a generic solution (supporting not just a function taking two ints and returning void) then you can use fake variadics to support a function taking any number of arguments up to an arbitrary fixed limit (this example supports up to three; VS2010's std::function can take up to ten).

void hook()
{
    std::cout << "Called\n";
}

template <typename R>
std::function<R()> wrap(std::function<R()> f)
{
    return [f]()->R { hook(); return f(); };
}

template <typename R, typename T0>
std::function<R(T0)> wrap(std::function<R(T0)> f)
{
    return [f](T0 v0)->R { hook(); return f(v0); };
}

template <typename R, typename T0, typename T1>
std::function<R(T0, T1)> wrap(std::function<R(T0, T1)> f)
{
    return [f](T0 v0, T1 v1)->R { hook(); return f(v0, v1); };
}

template <typename R, typename T0, typename T1, typename T2>
std::function<R(T0, T1, T2)> wrap(std::function<R(T0, T1, T2)> f)
{
    return [f](T0 v0, T1 v1, T2 v2)->R { hook(); return f(v0, v1, v2); };
}

template <typename F>
typename func_traits<F>::wrap_type wrap(F f)
{
    return wrap(typename func_traits<F>::wrap_type(f));
}

int main()
{
    auto cb = [](int a, int b){ std::cout << a << " " << b << "\n"; };
    auto wrap_cb = wrap(cb);
    wrap_cb(1, 2);
}

It's not especially efficient, due to the two layers of std::function (each one incurs the cost of a virtual call), but should be sufficient for most purposes until you can upgrade to a better compiler.

EDIT: Thanks to Martinho for pointing out my glaring mistake. The above code is fixed by the addition of another wrap() overload, which uses the following type traits code to select the correct specialization of std::function for the deduced functor type:

template <typename T>
struct func_traits;

template <typename R>
struct func_traits<R()>
{
    typedef std::function<R()> wrap_type;
};
template <typename R, typename A0>
struct func_traits<R(A0)>
{
    typedef std::function<R(A0)> wrap_type;
};
template <typename R, typename A0, typename A1>
struct func_traits<R(A0, A1)>
{
    typedef std::function<R(A0, A1)> wrap_type;
};
template <typename R, typename A0, typename A1, typename A2>
struct func_traits<R(A0, A1, A2)>
{
    typedef std::function<R(A0, A1, A2)> wrap_type;
};

template <typename R>
struct func_traits<R(*)()> : func_traits<R()> {};
template <typename R, typename A1>
struct func_traits<R(*)(A1)> : func_traits<R(A1)> {};
template <typename R, typename A1, typename A2>
struct func_traits<R(*)(A1, A2)> : func_traits<R(A1, A2)> {};
template <typename R, typename A1, typename A2, typename A3>
struct func_traits<R(*)(A1, A2, A3)> : func_traits<R(A1, A2, A3)> {};
template <typename R, typename C>
struct func_traits<R(C::*)()> : func_traits<R()> {};
template <typename R, typename A1, typename C>
struct func_traits<R(C::*)(A1)> : func_traits<R(A1)> {};
template <typename R, typename A1, typename A2, typename C>
struct func_traits<R(C::*)(A1, A2)> : func_traits<R(A1, A2)> {};
template <typename R, typename A1, typename A2, typename A3, typename C>
struct func_traits<R(C::*)(A1, A2, A3)> : func_traits<R(A1, A2, A3)> {};
template <typename R, typename C>
struct func_traits<R(C::*)() const> : func_traits<R()> {};
template <typename R, typename A1, typename C>
struct func_traits<R(C::*)(A1) const> : func_traits<R(A1)> {};
template <typename R, typename A1, typename A2, typename C>
struct func_traits<R(C::*)(A1, A2) const> : func_traits<R(A1, A2)> {};
template <typename R, typename A1, typename A2, typename A3, typename C>
struct func_traits<R(C::*)(A1, A2, A3) const> : func_traits<R(A1, A2, A3)> {};

template <typename T>
struct func_traits : func_traits<decltype(&T::operator())> {};

template <typename T>
struct func_traits<T&> : func_traits<T> {};
template <typename T>
struct func_traits<T&&> : func_traits<T> {};

Live example: http://coliru.stacked-crooked.com/a/1e5aeca5b01a36e6

Autres conseils

I'd make a wrapper for the callbacks that handles it like this:

#include <iostream>     
#include <functional>

typedef std::function<void(int param1, int param2)> TheCallback;

class CallbackWrapper
{
public:
    CallbackWrapper(TheCallback callback)
        : m_callback(callback)
    {}

    void startCallback(int a, int b)
    {
        std::cout << "Called" << std::endl;
        m_callback(a, b);
    }

private:
    TheCallback m_callback;
};

int main(int argc, char* argv[])
{
    TheCallback cb = [](int a, int b) { 
        std::cout << "Param 1:" << a << ", Param 2:" << b << std::endl;
    };

    CallbackWrapper testCallback(cb);

    testCallback.startCallback(11, 22);

    return system("pause");
}

You may even omit the local cb:

int main(int argc, char* argv[])
{
    CallbackWrapper testCallback([](int a, int b) { 
        std::cout << "Param 1:" << a << ", Param 2:" << b << std::endl;
    });

    testCallback.startCallback(11, 22);

    return system("pause");
}

After struggling with different options I came up to solution that replaces std::function<> with compatible template. Not perfect, but here what currently works for me:

template<class TFunc>
class hook_function : public std::function<TFunc>
{
public:
    typedef std::function<TFunc> FuncType;
    FuncType original;

    void hook()
    {
        std::cout << "Called." << std::endl;
    }

    hook_function() : FuncType()
    {}

    template<typename T>
    hook_function(T&& fn) 
        : original(std::forward<T>(fn)) 
        , FuncType(std::forward<T>(fn))
    {
    }

    hook_function(hook_function&& other) 
        : FuncType(static_cast<FuncType&&>(other))
    {
    }

    hook_function& operator=(hook_function&& other)
    {
        FuncType::operator=(static_cast<FuncType&&>(other));
        return *this;
    }

... insert more here ...

    void operator()(int a, int b)
    {
        hook();
        original(a, b);
    }

    void operator()(const std::string s)
    {
        hook();
        original(s);
    }
};
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top