I have C++ code that wraps an arbitrary lambda and returns the result of the lambda.
template <typename F> auto wrapAndRun(F fn) -> decltype(F()) { // foo(); auto result = fn(); // bar(); return result; }
This works unless F
returns void
(error: variable has incomplete type 'void'
). I thought of using a ScopeGuard
to run bar
, but I don't want bar
to run if fn
throws. Any ideas?
P.S. I found out later there's a proposal to fix this inconsistency.
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.
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; }
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