Say I have some wrapper function, in which I'd like to do some setup, call a callback (and save its result), do some cleanup, and then return what the callback returned:
#include <functional>
#include <utility>
template<class F, typename... Args>
decltype(auto) wrap(F&& func, Args&... args) {
// Do some stuff before
auto result = std::invoke(std::forward<decltype(func)>(func),
std::forward<Args>(args)...);
// Do some stuff after
return result;
}
A practical example of this is a timer utility function that returns the elapsed time of the function call as well as its return value (in a tuple, perhaps).
Such a function works fine for callables with return types:
void foo() {
auto a = 1;
wrap([](auto a) { return 1; }, a);
}
But with a callable with void return type, during the template specialization the compiler complains that auto result has incomplete type void:
void foo() {
auto a = 1;
wrap([](auto a) {}, a);
}
This makes sense of course, because while you can return void(), you can't store it in a variable.
I want wrap to work for both kinds of callables. I've attempted using std::function to give two signatures for wrap:
template<class T, typename... Args> decltype(auto) wrap(std::function<T(Args...)>, Args&... args)template<typename... Args> decltype(auto) wrap(std::function<void(Args...)>, Args&... args)The first of those will continue to match callables with a non-void return, but the latter fails to match those with return type void.
Is there a way to make wrap work in both the return type void and non-void callable cases?
Void functions do not have a return type, but they can do return values.
Within the body of the method, you use the return statement to return the value. Any method declared void doesn't return a value. It does not need to contain a return statement, but it may do so.
In Python, it is possible to compose a function without a return statement. Functions like this are called void, and they return None, Python's special object for "nothing". Here's an example of a void function: >>> def sayhello(who): print 'Hello,', who + '!'
A void function will automatically return to the caller at the end of the function. No return statement is required. Do not put a return statement at the end of a non-value returning function. In the above program, the value to be printed needs to be provided on the right-side of the std::cout << .
The way I like to solve this problem is with regular void. Well, we don't actually have proper regular void, but we can make our own type Void that is regular, that is kind of like void. And then provide a wrapper around invoke that understands that.
The shortest version (there's more detail in that blog):
struct Void { };
template <typename F, typename ...Args,
typename Result = std::invoke_result_t<F, Args...>,
std::enable_if_t<!std::is_void_v<Result>, int> = 0>
Result invoke_void(F&& f, Args&& ...args) {
return std::invoke(std::forward<F>(f), std::forward<Args>(args)...);
}
// void case
template <typename F, typename ...Args,
typename Result = std::invoke_result_t<F, Args...>,
std::enable_if_t<std::is_void_v<Result>, int> = 0>
Void invoke_void(F&& f, Args&& ...args) {
std::invoke(std::forward<F>(f), std::forward<Args>(args)...);
return Void();
}
This lets you, in your original code, do:
template<class F, typename... Args>
decltype(auto) wrap(F&& func, Args&... args) {
// Do some stuff before
auto result = invoke_void(std::forward<decltype(func)>(func),
std::forward<Args>(args)...);
// Do some stuff after
return result;
}
And this works, even if func returns void.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With