C++ function decorator

Just go full template, without std::function:

template< typename Func >
class FunctionDecorator
{
public:
  FunctionDecorator( Func func )
    : m_func( std::move(func) )
  {}

  void operator()()
  {
    // do some stuff prior to function call
    m_func();
    // do stuff after function call
  }

private:
  Func m_func;
};

template< typename Func >
FunctionDecorator<Func> decorate(Func func) {
  return FunctionDecorator<Func>(std::move(func));
}

Hmm. I may or may not have gone overkill.

#include <type_traits>
#include <utility>
#include <iostream>

template <class T>
struct RetWrapper {
    template <class Tfunc, class... Targs>
    RetWrapper(Tfunc &&func, Targs &&... args)
    : val(std::forward<Tfunc>(func)(std::forward<Targs>(args)...)) {}
    
    T &&value() { return static_cast<T &&>(val); }
    
private:
    T val;
};

template <>
struct RetWrapper<void> {
    template <class Tfunc, class... Targs>
    RetWrapper(Tfunc &&func, Targs &&... args) {
        std::forward<Tfunc>(func)(std::forward<Targs>(args)...);
    }
    
    void value() {}
};

template <class Tfunc, class Tbefore, class Tafter>
auto decorate(Tfunc &&func, Tbefore &&before, Tafter &&after) {
    return [
    
        func = std::forward<Tfunc>(func),
        before = std::forward<Tbefore>(before),
        after = std::forward<Tafter>(after)
        
    ] (auto &&... args) -> decltype(auto) {
            
        before(std::forward<decltype(args)>(args)...);
        RetWrapper<std::result_of_t<Tfunc(decltype(args)...)>> ret(
            func, std::forward<decltype(args)>(args)...
        );
        after(std::forward<decltype(args)>(args)...);
        
        return ret.value();
    };
}

/*
 * Tests
 */

float test1(float a, float b) {
    std::cout << "Inside test1\n";
    return a * b;
}

void test2() {
    std::cout << "Inside test2\n";
}

int i = 0;

int &test3() {
    return i;
}

int main() {

    auto test1Deco = decorate(
        test1,
        [] (float a, float b) {
            std::cout << "Calling test1 with " << a << " and " << b << '\n';
        },
        [] (float a, float b) {
            std::cout << "Called test1 with " << a << " and " << b << '\n';
        }
    );
    
    float c = test1Deco(3.5f, 5.1f);
    
    std::cout << "Yields " << c << '\n';
    
    auto test2Deco = decorate(
        test2,
        [] () {
            std::cout << "Calling test2\n";
        },
        [] () {
            std::cout << "Called test2\n";
        }
    );
    
    test2Deco();
    
    auto test3Deco = decorate(
        test3,
        [] () {
            std::cout << "Calling test3\n";
        },
        [] () {
            std::cout << "Called test3\n";
        }
    );
    
    auto &i2 = test3Deco();
    i2 = 42;
    
    std::cout << "Global i = " << i << '\n';
    
    return 0;
}

Output :

Calling test1 with 3.5 and 5.1
Inside test1
Called test1 with 3.5 and 5.1
Yields 17.85
Calling test2
Inside test2
Called test2
Calling test3
Called test3
Global i = 42