Обернуть С++ 11 std::function в другой std::function?

Я хотел бы обернуть результат std::bind() или лямбда во вспомогательную функцию, которая отслеживает время выполнения вызовов функции. Мне нужно обобщенное решение, которое будет работать с любым количеством параметров (и методов класса) и совместимо с С++ 11.

Мое намерение состоит в том, чтобы взять обернутую функцию и передать ее в boost::signals2::signal, поэтому результирующий объект функции должен быть идентичен по сигнатуре исходной функции.

В основном я ищу какой-то волшебный класс или функцию Wrapper, которая работает следующим образом:

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);

это время, сколько времени потребовалось, чтобы напечатать 42.

Спасибо!


person moof2k    schedule 26.01.2016    source источник
comment
Как бы вы хотели справиться со временем выполнения? Распечатать его на стандартный вывод напрямую?   -  person Lingxi    schedule 26.01.2016
comment
Вы можете сделать что-то вроде этого и делегировать большую часть тяжелой работы std:: функция.   -  person melak47    schedule 26.01.2016
comment
Работа с C++11 и boost.signals2: coliru   -  person melak47    schedule 26.01.2016
comment
@ melak47 ах - на самом деле не смотрел на твой образец. Трюк RAII в моем ответе позволяет избежать особого случая для возвращаемого типа void!   -  person sehe    schedule 26.01.2016


Ответы (3)


Я считаю, что то, что вы хотите сделать, можно решить с помощью вариативных шаблонов.

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

Вы можете в основном «переслать» список аргументов из вашей внешней функции std:: во внутреннюю.

РЕДАКТИРОВАТЬ:

Ниже я добавил минимальный рабочий пример, используя концепцию вариативного шаблона. В main(...) лямбда-функция заключена в другой объект std::function с использованием указанных параметров для внутренней лямбда-функции. Это делается путем передачи функции измерения в качестве параметра шаблонной функции measureTimeWrapper. Он возвращает функцию с той же сигнатурой, что и переданная функция (при условии, что вы правильно определили список параметров этой лямбды в аргументе шаблона measureTimeWrapper).

Функция, время выполнения которой измеряется, просто сидит здесь и ждет количество миллисекунд, определяемое ее параметром. Кроме этого, он вообще не связан с измерением времени. Это делается функцией-оболочкой.

Обратите внимание, что таким образом теряется возвращаемое значение внутренней функции; вы можете изменить способ возврата значений (возможно, в виде структуры, содержащей измеренное время и возвращаемое реальное значение), если хотите сохранить его.

Не забудьте скомпилировать свой код как минимум с -std=c++11.

#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;
}
person Community    schedule 26.01.2016
comment
Ответы только на ссылку не желательны на SO. Также cplusplus.com не считается основным ресурсом. - person sehe; 26.01.2016
comment
@sehe Я вижу этот момент и добавил пример рабочего кода и немного больше пояснений. Спасибо за подсказку. Хотя я не понимаю, почему cplusplus.com не может быть действительной ссылкой. - person ; 26.01.2016
comment
stackoverflow.com/questions/6520052/ и некоторые другие места - person sehe; 26.01.2016
comment
Ясно спасибо. Хотя проблем с этим никогда не было. - person ; 26.01.2016
comment
Это отлично и работает в С++ 11, но не соответствует моему требованию boost:: signal2. Тем не менее, если вы немного его измените (вернете void вместо time double), то он отлично работает. К сожалению, это не распространяется на какой-либо возвращаемый тип, но с сигналами мне все равно. Спасибо! - person moof2k; 26.01.2016
comment
Также мне пришлось изменить ваше решение для работы с clang-3.6. Пройти только args... не получилось, пришлось использовать std::forward<Args>(args)... - person moof2k; 26.01.2016

Если речь идет о производительности, я настоятельно рекомендую не оборачивать функции дважды.

Без них можно обойтись:

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)...);
    };
}

Смотрите живую демонстрацию:

Жить на 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();
}

Отпечатки (например):

 -- (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

ОБНОВЛЕНИЕ: версия С++ 11

Жить на 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();
}
person sehe    schedule 26.01.2016
comment
На самом деле, ваш мне нравится больше, чем мой, так как вам не требуются аргументы шаблона в timed. Приму к сведению, что на будущее std::forward и decltype — хорошая комбинация с вариативными шаблонами. - person ; 26.01.2016
comment
Я забыл упомянуть в своем посте, что я на С++ 11. Это отличное решение, но для возврата auto требуется С++ 14. Если я изменю вашу живую демонстрацию на std=c++11, она больше не будет компилироваться. - person moof2k; 26.01.2016
comment
@moof2k Нет проблем. Тогда это будет немного более подробно. Выложу позже сегодня вечером - person sehe; 26.01.2016
comment
@moof2k Ну вот, закончили до обеда, в спешке: Live On Coliru - person sehe; 26.01.2016

#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;
}

Замените "function tracked"part любой логикой отслеживания, которую вы хотите (время, кеш и т. д.).

Это требует c++14 хотя

person Drax    schedule 26.01.2016
comment
Это самый потрясающий ответ, но, к сожалению, я нахожусь на платформе, которая еще не поддерживает С++ 14. Спасибо! - person moof2k; 26.01.2016