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