Как написать лямбда-обертку для функции с необязательным возвращаемым значением - PullRequest
0 голосов
/ 13 декабря 2018

Я попытался написать лямбду, которая измеряет время выполнения произвольных функций.С большой помощью мне удалось это сделать для C ++ 14 и функций, имеющих возвращаемое значение, см. Измерение времени выполнения произвольных функций с помощью C ++ 14 lambda .

Тогда я хотелмой код также работает с C ++ 11, поэтому я реализовал ту же идею с шаблонными функциями.

Наконец, я понял, что этот код не работает для функций, не имеющих возвращаемого значения.Было довольно просто обобщить функции шаблона, чтобы включить измерение времени и для функций, возвращающих void.

Но я застрял, когда дело доходит до измерения лямбды.Компилятор жалуется, что необязательное возвращаемое значение, которое я хочу использовать, имеет неполный тип.Можно ли это исправить?

Вот мой код:

#include <chrono>
#include <iostream>
#include <set>

#include <boost/config.hpp>

#ifdef BOOST_NO_CXX14_GENERIC_LAMBDAS

/**
 * \brief Measures the time of arbitrary function calls.
 *
 * This template function works with C++11 and therefore it does not use
 * universal references.
 *
 * This is an internal helper template for functions returning void.
 *
 * \tparam Function function type
 * \tparam Parameters parameters type
 * \param enabled whether time measurement should be enabled
 * \param taskName name for printing the measured time
 * \param function function to measure
 * \param parameters function arguments
 *
 * \returns return value from given function
 */
template <typename Function, typename... Parameters>
auto measure(std::true_type, bool enabled,
        const std::string& taskName, Function function, Parameters... parameters) ->
        decltype(function(parameters...))
{
    std::chrono::steady_clock::time_point startTimePoint;

    if (enabled)
    {
        startTimePoint = std::chrono::steady_clock::now();
    }

    std::forward<decltype(function)>(function)(
            std::forward<decltype(parameters)>(parameters)...);

    if (enabled)
    {
        const std::chrono::steady_clock::time_point stopTimePoint =
                std::chrono::steady_clock::now();

        const std::chrono::duration<double> timeSpan =
                std::chrono::duration_cast<std::chrono::duration<double>>(
                        stopTimePoint - startTimePoint);

        std::cout << taskName << " took " << timeSpan.count() << " seconds." <<
                std::endl;
    }
}

/**
 * \brief Measures the time of arbitrary function calls.
 *
 * This template function works with C++11 and therefore it does not use
 * universal references.
 *
 * This is an internal helper template for functions returning non-void.
 *
 * \tparam Function function type
 * \tparam Parameters parameters type
 * \param enabled whether time measurement should be enabled
 * \param taskName name for printing the measured time
 * \param function function to measure
 * \param parameters function arguments
 *
 * \returns return value from given function
 */
template <typename Function, typename... Parameters>
auto measure(std::false_type, bool enabled,
        const std::string& taskName, Function function, Parameters... parameters) ->
        decltype(function(parameters...))
{
    std::chrono::steady_clock::time_point startTimePoint;

    if (enabled)
    {
        startTimePoint = std::chrono::steady_clock::now();
    }

    auto returnValue =
            std::forward<decltype(function)>(function)(
                    std::forward<decltype(parameters)>(parameters)...);

    if (enabled)
    {
        const std::chrono::steady_clock::time_point stopTimePoint =
                std::chrono::steady_clock::now();

        const std::chrono::duration<double> timeSpan =
                std::chrono::duration_cast<std::chrono::duration<double>>(
                        stopTimePoint - startTimePoint);

        std::cout << taskName << " took " << timeSpan.count() << " seconds." <<
                std::endl;
    }

    return returnValue;
}

template <typename Function, typename... Parameters>
using ReturnType = typename std::result_of<Function(Parameters...)>::type;

/**
 * \brief Measures the time of arbitrary function calls.
 *
 * This template function works with C++11 and therefore it does not use
 * universal references.
 *
 * \tparam Function function type
 * \tparam Parameters parameters type
 * \param enabled whether time measurement should be enabled
 * \param taskName name for printing the measured time
 * \param function function to measure
 * \param parameters function arguments
 *
 * \returns return value from given function
 */
template <typename Function, typename... Parameters>
auto measure(bool enabled, const std::string& taskName, Function function,
        Parameters... parameters) -> decltype(function(parameters...))
{
    return measure(std::is_void<ReturnType<Function, Parameters...>>{},
            enabled, taskName, function, parameters...);
}

#else

/**
 * \brief Measures the time of arbitrary function calls.
 *
 * This lambda works with C++14 and it accepts universal references.
 *
 * \param enabled whether time measurement should be enabled
 * \param taskName name for printing the measured time
 * \param function function to measure
 * \param parameters function arguments
 *
 * \returns return value from given function
 */
auto measure = [](bool enabled, const std::string& taskName, auto&& function,
        auto&&... parameters) -> decltype(auto)
{
    std::chrono::steady_clock::time_point startTimePoint;

    if (enabled)
    {
        startTimePoint = std::chrono::steady_clock::now();
    }

    decltype(auto) returnValue =
            std::forward<decltype(function)>(function)(
                    std::forward<decltype(parameters)>(parameters)...);

    if (enabled)
    {
        const std::chrono::steady_clock::time_point stopTimePoint =
                std::chrono::steady_clock::now();

        const std::chrono::duration<double> timeSpan =
                std::chrono::duration_cast<std::chrono::duration<double>>(
                        stopTimePoint - startTimePoint);

        std::cout << taskName << " took " << timeSpan.count() << " seconds." <<
                std::endl;
    }

    return returnValue;
};

#endif

int main(int, char**)
{
    measure(true, "Populating Ordered Set", []()
    {
        std::set<int> orderedSet;

        for (int i = 0; i < 1000; ++i)
        {
            orderedSet.insert(i);
        }
    });

 return 0;
}

Если он скомпилирован с помощью компилятора C ++ 11 (например, g ++ с -std = gnu ++ 11), он использует функцию шаблона и поэтому хорошо работает здесь.Если он скомпилирован с помощью компилятора C ++ 14 (-std = gnu ++ 14), он использует лямбду, и поэтому я получаю это сообщение об ошибке компиляции:

..\src\Main.cpp: In instantiation of '<lambda(bool, const string&, auto:1&&, auto:2&& ...)> [with auto:1 = main(int, char**)::<lambda()>; auto:2 = {}; std::__cxx11::string = std::__cxx11::basic_string<char>]':
..\src\Main.cpp:186:10:   required from here
..\src\Main.cpp:154:24: error: 'void returnValue' has incomplete type
                 decltype(auto) returnValue =
                                                ^~~~~~~~~~~    

Большое спасибо за любую помощь.

Ответы [ 3 ]

0 голосов
/ 13 декабря 2018

Используйте RAII вместо вашего времени:

struct Timer
{
    explicit Timer(bool enable) : enable(enable)
    {
        if (enabled)
        {
            startTimePoint = std::chrono::steady_clock::now();
        }
    }

    ~Timer()
    {
        if (enabled)
        {
            const std::chrono::steady_clock::time_point stopTimePoint =
                    std::chrono::steady_clock::now();

            const std::chrono::duration<double> timeSpan =
                    std::chrono::duration_cast<std::chrono::duration<double>>(
                            stopTimePoint - startTimePoint);

            std::cout << taskName << " took " << timeSpan.count() << " seconds." <<
                    std::endl;
        }
    }

    Timer(const Timer&) = delete;
    Timer& operator=(const Timer&) = delete;

    bool enable;
    std::chrono::steady_clock::time_point startTimePoint;
};

И тогда ваша функция станет:

template <typename Function, typename... Args>
auto measure(bool enabled, const std::string& taskName, Function&& function, Args&&... args)
->  decltype(std::forward<Function>(function)(std::forward<Args>(args)...))
{
    Timer timer(enabled);

    return std::forward<Function>(function)(std::forward<Args>(args)...);
}
0 голосов
/ 13 декабря 2018

С идеей использования RAII можно упростить и код шаблона.Для тех, кому это может пригодиться, я хотел бы показать свою окончательную версию:

#include <chrono>
#include <iostream>
#include <set>

#include <boost/config.hpp>

/**
 * \brief Internal timer that can be used to measure time with RAII.
 */
class InternalTimer
{
public:

    /**
     * \brief Instance creation starts the timer.
     *
     * \param enabled whether time measurement should be enabled
     * \param taskName name for printing the measured time
     */
    explicit InternalTimer(bool enabled, const std::string& taskName) :
            enabled(enabled), taskName(taskName)
    {
        if (enabled)
        {
            startTimePoint = std::chrono::steady_clock::now();
        }
    }

    /**
     * \brief Destructing the instance stops the timer and prints the measurement.
     */
    ~InternalTimer()
    {
        if (enabled)
        {
            const std::chrono::steady_clock::time_point stopTimePoint =
                    std::chrono::steady_clock::now();

            const std::chrono::duration<double> timeSpan = std::chrono::duration_cast<
                    std::chrono::duration<double>>(stopTimePoint - startTimePoint);

            std::cout << taskName << " took " << timeSpan.count() << " seconds."
                    << std::endl;
        }
    }

    /**
     * \brief Deleted copy constructor.
     */
    InternalTimer(const InternalTimer&) = delete;

    /**
     * \brief Deleted assignment operator.
     *
     * \returns reference to the object that is assigned to
     */
    InternalTimer& operator=(const InternalTimer&) = delete;

private:

    bool enabled;
    const std::string& taskName;
    std::chrono::steady_clock::time_point startTimePoint;
};

#ifdef BOOST_NO_CXX14_GENERIC_LAMBDAS

/**
 * \brief Measures the time of arbitrary function calls.
 *
 * This template function works with C++11 and therefore it does not use
 * universal references.
 *
 * \tparam Function function type
 * \tparam Parameters parameters type
 * \param enabled whether time measurement should be enabled
 * \param taskName name for printing the measured time
 * \param function function to measure
 * \param parameters function arguments
 *
 * \returns return value from given function
 */
template <typename Function, typename... Parameters>
auto measure(bool enabled, const std::string& taskName, Function function,
        Parameters... parameters) -> decltype(function(parameters...))
{
    InternalTimer timer(enabled, taskName);

    return std::forward<Function>(function)(
            std::forward<Parameters>(parameters)...);
}

#else

/**
 * \brief Measures the time of arbitrary function calls.
 *
 * This lambda works with C++14 and it accepts universal references.
 *
 * \param enabled whether time measurement should be enabled
 * \param taskName name for printing the measured time
 * \param function function to measure
 * \param parameters function arguments
 *
 * \returns return value from given function
 */
auto measure = [](bool enabled, const std::string& taskName, auto&& function,
        auto&&... parameters) -> decltype(auto)
{
    InternalTimer timer(enabled, taskName);

    return std::forward<decltype(function)>(function)(
            std::forward<decltype(parameters)>(parameters)...);
};

#endif

int main(int, char**)
{
    measure(true, "Populating Ordered Set", []()
    {
        std::set<int> orderedSet;

        for (int i = 0; i < 1000; ++i)
        {
            orderedSet.insert(i);
        }
    });

    return 0;
}
0 голосов
/ 13 декабря 2018

, если он скомпилирован с помощью компилятора C ++ 14 (-std = gnu ++ 14), он использует лямбду, и поэтому я получаю это сообщение об ошибке компиляции

Позвольте мнеупростите вашу функцию в следующем псевдокоде

auto measure = [](someArguments....) -> decltype(auto)
{
    something1();

    decltype(auto) returnValue = somethingThatCanReturnVoid();

    something2();

    return returnValue;
};

Проблема в том, что somethingThatCanReturnVoid() возвращает void, потому что вы не можете определить void переменную.

Вы можете использовать следующеефакты

(1) вы не можете определить void переменную, но вы можете написать return foo();, где foo() - функция, возвращающая void

(2), если вы напишитеreturn foo(), уничтожение объекта, ограниченного в функции, выполняется после выполнения foo()

. На этом этапе решение кажется мне очевидным: создайте объект типа Bar() и выполните something2()в Bar деструктор.

Что-то следующее (псевдокод)

auto measure = [](someArguments....) -> decltype(auto)
{
  Bar b{otherArguments...}; // something1() in contruction;
                            // something2() in destruction;

  return somethingThatCanReturnVoid();
};

Таким образом, something1() выполняется до somethingThatCanReturnVoid(), something2() выполняется после, а компилятор нене жалуются на

  return somethingThatCanReturnVoid();

, что совершенно законно, даже когда somethingThatCanReturnVoid() возврат void

...