2

I'd like to wrap the result of a std::bind() or a lambda in a helper function that tracks the execution time of calls to the function. I'd like a generalized solution that will work with any number of parameters (and class methods) and is c++11 compatible.

My intent is to take the wrapped function and pass it to a boost::signals2::signal so the resulting function object needs to be identical in signature to the original function.

I'm basically looking for some magical class or function Wrapper that works like this:

std::function<void(int)> f = [](int x) {
    std::cerr << x << std::endl;
};

boost::signals2::signal<void(int)> x_signal;
x_signal.connect(Wrapper<void(int)>(f));
x_signal(42);

that would time how long it took to print 42.

Thanks!

moof2k
  • 1,678
  • 1
  • 17
  • 19
  • How would you like to deal with the execution time? Print it to standard output directly? – Lingxi Jan 26 '16 at 06:01
  • You can do [something like this](http://coliru.stacked-crooked.com/a/5f378276710b6e7b), and delegate most of the hard work to std::function. – melak47 Jan 26 '16 at 09:12
  • Working with C++11 and boost.signals2: [coliru](http://coliru.stacked-crooked.com/a/be4ec332a0c5a061) – melak47 Jan 26 '16 at 09:38
  • @melak47 ah - didn't actually look at your sample. The RAII trick in my answer avoids the special case for `void` return type! – sehe Jan 26 '16 at 12:35

3 Answers3

7

If it's about performance, I strongly suggest not to doubly wrap functions.

You can do without those:

template <typename Caption, typename F>
auto timed(Caption const& task, F&& f) {
    return [f=std::forward<F>(f), task](auto&&... args) {
        using namespace std::chrono;

        struct measure {
            high_resolution_clock::time_point start;
            Caption const& task;
            ~measure() { std::cout << " -- (" << task << " completed in " << duration_cast<microseconds>(high_resolution_clock::now() - start).count() << "µs)\n"; }
        } timing { high_resolution_clock::now(), task };

        return f(std::forward<decltype(args)>(args)...);
    };
}

See live demo:

Live On Coliru

#include <chrono>
#include <iostream>

template <typename Caption, typename F>
auto timed(Caption const& task, F&& f) {
    return [f=std::forward<F>(f), task](auto&&... args) {
        using namespace std::chrono;

        struct measure {
            high_resolution_clock::time_point start;
            Caption const& task;
            ~measure() { std::cout << " -- (" << task << " completed in " << duration_cast<microseconds>(high_resolution_clock::now() - start).count() << "µs)\n"; }
        } timing { high_resolution_clock::now(), task };

        return f(std::forward<decltype(args)>(args)...);
    };
}

#include <thread>

int main() {
    using namespace std;
    auto f = timed("IO", [] { cout << "hello world\n"; return 42; });
    auto g = timed("Sleep", [](int i) { this_thread::sleep_for(chrono::seconds(i)); });

    g(1);
    f();
    g(2);

    std::function<int()> f_wrapped = f;
    return f_wrapped();
}

Prints (e.g.):

 -- (Sleep completed in 1000188µs)
hello world
 -- (IO completed in 2µs)
 -- (Sleep completed in 2000126µs)
hello world
 -- (IO completed in 1µs)
exitcode: 42

UPDATE: c++11 version

Live On Coliru

#include <chrono>
#include <iostream>

namespace detail {

    template <typename F>
    struct timed_impl {
        std::string _caption;
        F _f;

        timed_impl(std::string const& task, F f) 
            : _caption(task), _f(std::move(f)) { }

        template <typename... Args>
        auto operator()(Args&&... args) const -> decltype(_f(std::forward<Args>(args)...))
        {
            using namespace std::chrono;

            struct measure {
                high_resolution_clock::time_point start;
                std::string const& task;
                ~measure() { std::cout << " -- (" << task << " completed in " << duration_cast<microseconds>(high_resolution_clock::now() - start).count() << "µs)\n"; }
            } timing { high_resolution_clock::now(), _caption };

            return _f(std::forward<decltype(args)>(args)...);
        }
    };
}

template <typename F>
detail::timed_impl<F> timed(std::string const& task, F&& f) {
    return { task, std::forward<F>(f) };
}

#include <thread>

int main() {
    using namespace std;
    auto f = timed("IO", [] { cout << "hello world\n"; return 42; });
    auto g = timed("Sleep", [](int i) { this_thread::sleep_for(chrono::seconds(i)); });

    g(1);
    f();
    g(2);

    std::function<int()> f_wrapped = f;
    return f_wrapped();
}
sehe
  • 374,641
  • 47
  • 450
  • 633
  • Actually, I like yours better than mine as you don't require template arguments in `timed`. Will take note of that for the future, `std::forward` and `decltype` are a good combination with variadic templates. –  Jan 26 '16 at 12:16
  • I forgot to mention in my post I'm on c++11. This is a great solution but returning auto requires c++14. If I change your live demo to std=c++11 it no longer compiles. – moof2k Jan 26 '16 at 17:31
  • @moof2k No problem. It'll be slightly more verbose then. Will post later tonight – sehe Jan 26 '16 at 17:32
  • @moof2k There you go, finished before dinner, in a hurry: [Live On Coliru](http://coliru.stacked-crooked.com/a/21e2a31fa0662483) – sehe Jan 26 '16 at 17:45
2

I believe what you want to do can be solved with variadic templates.

http://www.cplusplus.com/articles/EhvU7k9E/

You can basically "forward" the argument list from your outer std::function to the inner.

EDIT:

Below, I added a minimal working example using the variadic template concept. In main(...), a lambda function is wrapped into another std::function object, using the specified parameters for the inner lambda function. This is done by passing the function to measure as a parameter to the templated function measureTimeWrapper. It returns a function with the same signature as the function passed in (given that you properly define that lambda's parameter list in the template argument of measureTimeWrapper).

The function who's running time is measured just sits here and waits for a number of milliseconds defined by its parameter. Other than that, it is not at all concerned with time measuring. That is done by the wrapper function.

Note that the return value of the inner function is lost this way; you might want to change the way values are returned (maybe as a struct, containing the measured time and the real return value) if you want to keep it.

Remember to compile your code with -std=c++11 at least.

#include <iostream>
#include <cstdlib>
#include <functional>
#include <chrono>
#include <thread>


template<typename T, typename... Args>
std::function<double(Args...)> measureTimeWrapper(std::function<T> fncFunctionToMeasure) {
  return [fncFunctionToMeasure](Args... args) -> double {
    auto tsStart = std::chrono::steady_clock::now();
    fncFunctionToMeasure(args...);
    auto tsEnd = std::chrono::steady_clock::now();

    std::chrono::duration<double> durTimeTaken = tsEnd - tsStart;

    return durTimeTaken.count();
  };
}

int main(int argc, char** argv) {
  std::function<double(int)> fncMeasured = measureTimeWrapper<void(int), int>([](int nParameter) {
      std::cout << "Process function running" << std::endl;

      std::chrono::milliseconds tsTime(nParameter); // Milliseconds
      std::this_thread::sleep_for(tsTime);
    });

  std::cout << "Time taken: " << fncMeasured(500) << " sec" << std::endl;

  return EXIT_SUCCESS;
}
  • 1
    Link only answers are not desired on SO. Also cplusplus.com isn't widely viewed as a prime resource. – sehe Jan 26 '16 at 11:36
  • @sehe I see that point and added a working code example and a bit more explanation. Thanks for the hint. Though, I don't see why cplusplus.com shouldn't be a valid reference. –  Jan 26 '16 at 12:11
  • http://stackoverflow.com/questions/6520052/whats-wrong-with-cplusplus-com and some other places – sehe Jan 26 '16 at 13:23
  • I see, thanks. Never had any problems with it, though. –  Jan 26 '16 at 13:27
  • This is excellent and works in c++11 but doesn't meet my boost::signals2 requirement. Still, if you change it slightly (return void instead of the time double) then it works great. It's unfortunate this doesn't generalize to any return type but with signals I don't care anyways. Thank you! – moof2k Jan 26 '16 at 17:34
  • Also I had to modify your solution to work with clang-3.6. Passing only `args...` didn't work, I had to use `std::forward(args)...` – moof2k Jan 26 '16 at 17:47
1
#include <iostream>
#include <functional>

template<typename Signature>
std::function<Signature>    Wrapper(std::function<Signature> func)
{
  return [func](auto... args)
    {
      std::cout << "function tracked" << std::endl;
      return func(args...);
    };
}

int     lol(const std::string& str)
{
  std::cout << str << std::endl;

  return 42;
}

int     main(void)
{
  auto wrapped = Wrapper<int(const std::string&)>(lol);

  std::cout << wrapped("Hello") << std::endl;
}

Replace the "function tracked"part with whatever tracking logic you want (timing, cache, etc.)

This requires c++14 though

Drax
  • 12,682
  • 7
  • 45
  • 85
  • This is the most awesome answer but sadly I'm on a platform that doesn't support c++14 yet. Thank you! – moof2k Jan 26 '16 at 17:16