问题
I have a template class that must perform some operation before calling a function whose parameters and return type are generic.
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's compiling correctly when ReturnType
is not void
.
When I use it in this context:
function<void>(firstArg, secondArg);
The compiler responds with
error: return-statement with a value, in function returning 'void' [-fpermissive]
pointing to the line marked with [1].
Is there any solution other than passing -fpermissive
to the compiler?
I would prefer to have a unique method, because I possible solution I found is to instantiate different versions using enable_if
and is_same
.
Thank you in advance.
-- Update --
This is a complete example. I should have said that our functions are indeed class 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 issue: Use std::bind(&Caller::callImpl<ReturnType>, this, args)
.
回答1:
Here's my attempt at a general C++11-compliant solution that you can easily reuse.
Let's start by creating a simple type trait that converts void
to an empty struct. This doesn't introduce any 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 converting an eventual void
return type 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)...);
}
Usage:
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's a proposal by Matt Calabrese called "Regular Void" that would solve this issue. You can find it here: "P0146R1".
回答2:
Depending on what you wish to accomplish in the lines
// dismiss the call
you might be able to use:
template <typename ReturnType, typename ...Args>
ReturnType function (Args ...args) {
// prepare for call
// ...
CallDismisser c;
return makeCall(args...); // [1]
}
That would work as long as the destructor of CallDismisser
can do everything you need to do.
回答3:
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)...);
}
);
}
so wrapped_invoker
takes a function object, and makes it return 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
lets you hold the return value and convert back to void
if needed. You must create holder<T>
using {}
to get reference lifetime extension to work properly. Adding a ctor to holder<T>
will break it.
holder<void>
silently discards anything 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>
holds either nothing or the return value of makeCall(args...)
.
If it holds nothing, rv.get()
returns void, and it is legal to return void to a function where ReturnValue
is void
.
Basically we are doing two tricks. First, we are preventing makeCall
from returning void
, and second if we are returning void
we are discarding the return value of makeCall
conditionally.
overload
isn't written here, but it is a function that takes 1 or more function objects (such as lambdas) and returns their overload set. There is a proposal for std::overload
, and a myriad of examples on stackoverflow itself.
Here is some:
- Overloaded lambdas in C++ and differences between clang and gcc
- C++11 “overloaded lambda” with variadic template and variable capture
回答4:
The problem seems to be with //Dismiss the call
.
This code shouldn't exist. That's what we have RAII for. The following code does work, 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"; }
来源:https://stackoverflow.com/questions/42725427/handling-a-void-variable-in-a-templatized-function-in-c11