I am on the journey to make a python like decorator with the latest available C++ techniques. I have seen some solution already here (Python-like C++ decorators), but I wonder if it can be done better. With help from others (Constructing std::function argument from lambda) I came up with the following solution.
template<typename TWrapped>
auto DurationAssertDecorator(const std::chrono::high_resolution_clock::duration& maxDuration, TWrapped&& wrapped)
{
return [wrapped = std::forward<TWrapped>(wrapped), maxDuration](auto&&... args)
{
const auto startTimePoint = std::chrono::high_resolution_clock::now();
static_assert(std::is_invocable<TWrapped, decltype(args)...>::value, "Wrapped object must be invocable");
if constexpr (!(std::is_void<decltype(wrapped(std::forward<decltype(args)>(args)...))>::value))
{
// return by reference will be here not converted to return by value?
//auto result = wrapped(std::forward<decltype(args)>(args)...);
decltype(wrapped(std::forward<decltype(args)>(args)...)) result = wrapped(std::forward<decltype(args)>(args)...);
const auto endTimePoint = std::chrono::high_resolution_clock::now();
const auto callDuration = endTimePoint - startTimePoint;
assert(callDuration <= maxDuration);
return result;
}
else
{
wrapped(std::forward<decltype(args)>(args)...);
const auto endTimePoint = std::chrono::high_resolution_clock::now();
const auto callDuration = endTimePoint - startTimePoint;
assert(callDuration <= maxDuration);
}
};
}
I do not use below "auto" on purpose to make sure that the return type is what I expect (or at least compatible).
I shall be able use it with any callable: stateless lambda, statefull lambda, struct functor, function pointer, std::function
std::function<double(double)> decorated = DurationAssertDecorator(1s, [](const double temperature) { return temperature + 5.0; });
double a = decorated (4);
Composition shall be OK, too:
std::function<double()> wrapped = LogDecorator(logger, [] { return 4.0; });
std::function<double()> wrapped_wrapped = DurationAssertDecorator(1s, functor);
This shall be not OK - int literal 5 is not a callable:
std::function<void(double)> decorated = DurationAssertDecorator(1s, 5);
So far it does the trick, however:
- The case - where the wrapped function has a return value - I was not sure if I just get the result by auto and the return value of the wrapped is a reference. If so then a copy will take place instead keeping the reference (return by pointer and by value shall be OK). So that is why I came up with that strange construct. Can I do it better?
- What other improvements/fixes are possible?