Processing a void variable in a templated function in C ++ 11

I have a template class that needs to perform some operation before calling a function whose parameters and return type are common.

This is the method:

template <typename ReturnType, typename ...Args> ReturnType function (Args ...args) { // prepare for call // ... ReturnType rv = makeCall(args...); // [1] // dismiss the call // ... return rv; } 

Of course, it compiles correctly when ReturnType not void . When I use it in this context:

 function<void>(firstArg, secondArg); 

The compiler is responsible

error: return-statement with a value, in function returning 'void' [-fpermissive]

pointing to the line marked [1].

Is there any solution other than passing -fpermissive compiler? I would prefer to have a unique method because I found a possible solution to create different versions using enable_if and is_same .

Thanks in advance.

- Update -

This is a complete example. I should have said that our functions are really cool methods.

 #include <type_traits> #include <iostream> class Caller { public: Caller() {} template <typename ReturnType, typename ...Arguments> ReturnType call(Arguments ... args) { prepare(); ReturnType rv = callImpl<ReturnType>(args...); done(); return rv; } private: void prepare() { std::cout << "Prepare\n"; } void done() { std::cout << "Done\n"; } template <typename ReturnType, typename ...Arguments> typename std::enable_if<std::is_same<ReturnType, void>::value, ReturnType>::type callImpl ( Arguments ... args) { std::cout << "Calling with void\n"; return; } template <typename ReturnType, typename ...Arguments> typename std::enable_if<std::is_same<ReturnType, bool>::value, ReturnType>::type callImpl (Arguments ... args) { std::cout << "Calling with bool\n"; return true; } template <typename ReturnType, typename ...Arguments> typename std::enable_if<std::is_same<ReturnType, int>::value, ReturnType>::type callImpl (Arguments ... args) { std::cout << "Calling with int\n"; return 42; } }; int main(int argc, char *argv[]) { Caller c; auto rbool = c.call<bool> (1,20); std::cout << "Return: " << rbool << "\n"; auto rint = c.call<int> (1,20); std::cout << "Return: " << rint << "\n"; // the next line fails compilation. compile with --std=c++11 c.call<void>("abababa"); return 0; } 

- Update -

Not a big problem: use std::bind(&Caller::callImpl<ReturnType>, this, args) .

+5
source share
4 answers

Here is my attempt to create a generic C ++ 11 compliant solution that can be easily reused.

Let's start by creating a simple type trait that converts void to an empty structure. This does not lead to repeated code repetition.

 struct nothing { }; template <typename T> struct void_to_nothing { using type = T; }; template <> struct void_to_nothing<void> { using type = nothing; }; template <typename T> using void_to_nothing_t = typename void_to_nothing<T>::type; 

We also need a way to call an arbitrary function that converts the return type of void to nothing :

 template <typename TReturn> struct helper { template <typename TF, typename... Ts> TReturn operator()(TF&& f, Ts&&... xs) const { return std::forward<TF>(f)(std::forward<Ts>(xs)...); } }; template <> struct helper<void> { template <typename TF, typename... Ts> nothing operator()(TF&& f, Ts&&... xs) const { std::forward<TF>(f)(std::forward<Ts>(xs)...); return nothing{}; } }; template <typename TF, typename... Ts> auto with_void_to_nothing(TF&& f, Ts&&... xs) -> void_to_nothing_t< decltype(std::forward<TF>(f)(std::forward<Ts>(xs)...))> { using return_type = decltype(std::forward<TF>(f)(std::forward<Ts>(xs)...)); return helper<return_type>{}(std::forward<TF>(f), std::forward<Ts>(xs)...); } 

Using:

 template <typename ReturnType, typename ...Args> void_to_nothing_t<ReturnType> function (Args ...args) { // prepare for call // ... auto rv = with_void_to_nothing(makeCall, args...); // [1] // dismiss the call // ... return rv; } 

live wandbox example


There, the proposal of Matt Calaberes is called "Regular Void" , which would solve this problem. You can find it here: "P0146R1" .

+5
source

Depending on what you want to execute in the lines

 // dismiss the call 

you could use:

 template <typename ReturnType, typename ...Args> ReturnType function (Args ...args) { // prepare for call // ... CallDismisser c; return makeCall(args...); // [1] } 

This will work as long as the CallDismisser destructor can do everything you need.

+5
source
 struct nothing {}; template<class Sig> using returns_void = std::is_same< std::result_of_t<Sig>, void >; template<class Sig> using enable_void_wrap = std::enable_if_t< returns_void<Sig>{}, nothing >; template<class Sig> using disable_void_wrap = std::enable_if_t< !returns_void<Sig>{}, std::result_of_t<Sig> >; template<class F> auto wrapped_invoker( F&& f ) { return overload( [&](auto&&...args)->enable_void_wrap<F(decltype(args)...)> { std::forward<F>(f)(decltype(args)(args)...); return {}; }, [&](auto&&...args)->disable_void_wrap<F(decltype(args)...)> { return std::forward<F>(f)(decltype(args)(args)...); } ); } 

therefore wrapped_invoker accepts a function object and returns it nothing instead of void .

Next, holder :

 template<class T> struct holder { T t; T&& get()&& { return std::forward<T>(t); } }; template<> struct holder<void> { template<class T> holder(T&&) {} // discard void get()&& {} }; 

holder allows you to hold the return value and convert back to void , if necessary. You must create a holder<T> with {} to get the link's normal life extension for it to work properly. Adding ctor to holder<T> will break it.

holder<void> silently discards everything that is passed to it.

 template <typename ReturnType, typename ...Args> ReturnType function (Args ...args) { // prepare for call // ... holder<ReturnType> rv{ wrapped_invoker(makeCall)(args...) }; // dismiss the call // ... return std::move(rv).get(); } 

Now holder<ReturnType> contains either nothing or the return value of makeCall(args...) .

If it does not contain anything, rv.get() returns void, and it is legal to return void in a function where ReturnValue is void .

We mainly do two tricks. Firstly, we prevent the return of makeCall void , and secondly, if we return void , we conditionally discard the return value of makeCall .

overload is not written here, but it is a function that takes 1 or more function objects (for example, lambdas) and returns their overload set. There is a suggestion for std::overload and many examples on the stack itself.

Here are some of them:

+4
source

The problem seems to be related to //Dismiss the call .

This code should not exist. This is what we have RAII for. The following code works even with ReturnType = void .

 template <typename ReturnType, typename ...Arguments> ReturnType call(Arguments ... args) { Context cx; return callImpl<ReturnType>(args...); } Context::Context() { std::cout << "prepare\n"; } Context::~Context() { std::cout << "done\n"; } 
+1
source

Source: https://habr.com/ru/post/1265310/


All Articles