1

我试图编写一个 lambda 来测量任意函数的执行时间。在很多帮助下,我已经为 C++14 和具有返回值的函数进行了管理,请参阅使用 C++14 lambda 测量任意函数的执行时间

然后我希望我的代码也可以与 C++11 一起使用,因此我用模板函数实现了相同的想法。

最后我意识到这段代码不适用于没有返回值的函数。泛化模板函数以启用时间测量对于返回 void 的函数来说非常简单。

但是当谈到测量 lambda 时,我被困住了。编译器抱怨我要使用的可选返回值的类型不完整。有可能解决这个问题吗?

这是我的代码:

#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 编译器编译的(比如带有 -std=gnu++11 的 g++),它使用模板函数,因此在这里运行良好。如果它是使用 C++14 编译器 (-std=gnu++14) 编译的,它使用 lambda,因此我收到以下编译错误消息:

..\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 =
                                                ^~~~~~~~~~~    

非常感谢您的帮助。

4

3 回答 3

2

使用 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)...);
}
于 2018-12-13T13:55:11.923 回答
1

if it is compiled with a C++14 compiler (-std=gnu++14), it uses the lambda and therefore I get this compilation error message

Let me simplify your function in the following pseudocode

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

    decltype(auto) returnValue = somethingThatCanReturnVoid();

    something2();

    return returnValue;
};

The problem is when somethingThatCanReturnVoid() return void because you can't define a void variable.

You can use the following facts

(1) you can't define a void variable but you can write return foo(); where foo() is a function returning void

(2) if you write return foo(), the destruction of object scoped in the function is executed after the execution of foo()

At this point, the solution seems me obvious: create an object of type Bar() and execute something2() in Bar destructor.

Something as follows (pseudocode)

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

  return somethingThatCanReturnVoid();
};

This way, something1() is executed before somethingThatCanReturnVoid(), something2() is executed after and the compiler doesn't complain for

  return somethingThatCanReturnVoid();

that is perfectly legal also when somethingThatCanReturnVoid() return void

于 2018-12-13T13:46:15.327 回答
0

有了使用 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;
}
于 2018-12-13T14:40:57.577 回答