Handling void assignment in C++ generic programming
Another trick might be to exploit the comma operator, something like:
struct or_void {};
template<typename T>
T&& operator,( T&& x, or_void ){ return std::forward<T>(x); }
template <typename F>
auto wrapAndRun(F fn) -> decltype(fn()) {
// foo();
auto result = ( fn(), or_void() );
// bar();
return decltype(fn())(result);
}
The new C++17 if constexpr
addition may be helpful here. You can choose whether to return fn()
's result at compile-time:
#include <type_traits>
template <typename F>
auto wrapAndRun(F fn) -> decltype(fn())
{
if constexpr (std::is_same_v<decltype(fn()), void>)
{
foo();
fn();
bar();
}
else
{
foo();
auto result = fn();
bar();
return result;
}
}
As you said C++2a is an option as well, you could also make use of concepts, putting a constraint on the function:
template <typename F>
requires requires (F fn) { { fn() } -> void }
void wrapAndRun(F fn)
{
foo();
fn();
bar();
}
template <typename F>
decltype(auto) wrapAndRun(F fn)
{
foo();
auto result = fn();
bar();
return result;
}
You can write a simple wrapper class that handles this part of it:
template <class T>
struct CallAndStore {
template <class F>
CallAndStore(F f) : t(f()) {}
T t;
T get() { return std::forward<T>(t); }
};
And specialize:
template <>
struct CallAndStore<void> {
template <class F>
CallAndStore(F f) { f(); }
void get() {}
};
You can improve usability with a small factory function:
template <typename F>
auto makeCallAndStore(F&& f) -> CallAndStore<decltype(std::declval<F>()())> {
return {std::forward<F>(f)};
}
Then use it.
template <typename F>
auto wrapAndRun(F fn) {
// foo();
auto&& result = makeCallAndStore(std::move(fn));
// bar();
return result.get();
}
Edit: with the std::forward
cast inside get
, this also seems to handle returning a reference from a function correctly.
You can have a look at the ScopeGuard by Alexandrescu: ScopeGuard.h It executes code only when there was no exception.
template<class Fn>
decltype(auto) wrapAndRun(Fn&& f) {
foo();
SCOPE_SUCCESS{ bar(); }; //Only executed at scope exit when there are no exceptions.
return std::forward<Fn>(f)();
}
So in case of no errors, the execution order is: 1. foo(), 2. f(), 3. bar(). And in case of an exception the order is: 1. foo(), 2. f()