Function argument returning void or non-void type
The best way to do this, in my opinion, is to actually change the way you call your possibly-void-returning functions. Basically, we change the ones that return void
to instead return some class type Void
that is, for all intents and purposes, the same thing and no users really are going to care.
struct Void { };
All we need to do is to wrap the invocation. The following uses C++17 names (std::invoke
and std::invoke_result_t
) but they're all implementable in C++14 without too much fuss:
// normal case: R isn't void
template <typename F, typename... Args,
typename R = std::invoke_result_t<F, Args...>,
std::enable_if_t<!std::is_void<R>::value, int> = 0>
R invoke_void(F&& f, Args&&... args) {
return std::invoke(std::forward<F>(f), std::forward<Args>(args)...);
}
// special case: R is void
template <typename F, typename... Args,
typename R = std::invoke_result_t<F, Args...>,
std::enable_if_t<std::is_void<R>::value, int> = 0>
Void invoke_void(F&& f, Args&&... args) {
// just call it, since it doesn't return anything
std::invoke(std::forward<F>(f), std::forward<Args>(args)...);
// and return Void
return Void{};
}
The advantage of doing it this way is that you can just directly write the code you wanted to write to begin with, in the way you wanted to write it:
template<class F>
auto foo(F &&f) {
auto result = invoke_void(std::forward<F>(f), /*some args*/);
//do some generic stuff
return result;
}
And you don't have to either shove all your logic in a destructor or duplicate all of your logic by doing specialization. At the cost of foo([]{})
returning Void
instead of void
, which isn't much of a cost.
And then if Regular Void is ever adopted, all you have to do is swap out invoke_void
for std::invoke
.
if you can place the "some generic stuff" in the destructor of a bar
class (inside a security try/catch block, if you're not sure that doesn't throw exceptions, as pointed by Drax), you can simply write
template <typename F>
auto foo (F &&f)
{
bar b;
return std::forward<F>(f)(/*some args*/);
}
So the compiler compute f(/*some args*/)
, exec the destructor of b
and return the computed value (or nothing).
Observe that return func();
, where func()
is a function returning void
, is perfectly legal.
Some specialization, somewhere, is necessary. But the goal here is to avoid specializing the function itself. However, you can specialize a helper class.
Tested with gcc 9.1 with -std=c++17
.
#include <type_traits>
#include <iostream>
template<typename T>
struct return_value {
T val;
template<typename F, typename ...Args>
return_value(F &&f, Args && ...args)
: val{f(std::forward<Args>(args)...)}
{
}
T value() const
{
return val;
}
};
template<>
struct return_value<void> {
template<typename F, typename ...Args>
return_value(F &&f, Args && ...args)
{
f(std::forward<Args>(args)...);
}
void value() const
{
}
};
template<class F>
auto foo(F &&f)
{
return_value<decltype(std::declval<F &&>()(2, 4))> r{f, 2, 4};
// Something
return r.value();
}
int main()
{
foo( [](int a, int b) { return; });
std::cout << foo( [](int a, int b) { return a+b; }) << std::endl;
}