问题
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