How to Write a Lambda Wrapping a Function with Optional Return Value

我只是一个虾纸丫 提交于 2021-01-05 11:46:45

问题


I have tried to write a lambda that measures the execution time of arbitrary functions. With a lot of help I have managed that for C++14 and functions having a return value, see Measure execution time of arbitrary functions with C++14 lambda.

Then I wanted my code to also work with C++11, therefore I have implemented the same idea with template functions.

Finally I have realized that this code does not work for functions having no return value. It has been quite simple to generalize the template functions to enable time measurement also for functions returning void.

But I am stuck when it comes to the measurement lambda. The compiler complains that the optional return value that I want to use has incomplete type. Is it possible to fix that?

Here is my code:

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

If it is compiled with a C++11 compiler (like g++ with -std=gnu++11), it uses the template function and therefore works well here. If it is compiled with a C++14 compiler (-std=gnu++14), it uses the lambda and therefore I get this compilation error message:

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

Thank you very much for any help.


回答1:


Use RAII instead for your timing:

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

And then your function becomes:

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



回答2:


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




回答3:


With the idea to use RAII it is possible to simplify also the template code. For those who might find it handy I would like to reveal my final version:

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


来源:https://stackoverflow.com/questions/53762636/how-to-write-a-lambda-wrapping-a-function-with-optional-return-value

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!