Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Handling void assignment in C++ generic programming

Tags:

c++

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.

like image 456
Ambarish Sridharanarayanan Avatar asked Dec 27 '17 17:12

Ambarish Sridharanarayanan


2 Answers

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.

like image 142
Nir Friedman Avatar answered Sep 19 '22 08:09

Nir Friedman


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; } 
like image 33
Mário Feroldi Avatar answered Sep 19 '22 08:09

Mário Feroldi