在 C++11 的模板化函数中处理 void 变量
Handling a void variable in a templatized function in C++11
我有一个模板 class,它必须在调用其参数和 return 类型为通用的函数之前执行一些操作。
这是方法:
template <typename ReturnType, typename ...Args>
ReturnType function (Args ...args) {
// prepare for call
// ...
ReturnType rv = makeCall(args...); // [1]
// dismiss the call
// ...
return rv;
}
当然,当 ReturnType
不是 void
时编译正确。
当我在这种情况下使用它时:
function<void>(firstArg, secondArg);
编译器响应
error: return-statement with a value, in function returning 'void' [-fpermissive]
指向标有[1]的行。
除了将 -fpermissive
传递给编译器之外,还有其他解决方案吗?
我更愿意有一个独特的方法,因为我找到的可能的解决方案是使用 enable_if
和 is_same
.
实例化不同的版本
提前致谢。
-- 更新--
这是一个完整的例子。我应该说我们的功能确实是 class 方法。
#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;
}
-- 更新--
不是大问题:使用 std::bind(&Caller::callImpl<ReturnType>, this, args)
。
这是我对通用的 C++11 兼容解决方案的尝试,您可以轻松地重复使用它。
让我们从创建一个简单的 type trait 开始,它将 void
转换为空结构。这不会引入任何代码重复。
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;
我们还需要一种方法来调用任意函数,将最终的 void
return 类型转换为 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)...);
}
用法:
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;
}
Matt Calabrese 提出了一项名为 "Regular Void" 的提案,可以解决此问题。 You can find it here: "P0146R1".
取决于您希望在行中完成什么
// dismiss the call
您或许可以使用:
template <typename ReturnType, typename ...Args>
ReturnType function (Args ...args) {
// prepare for call
// ...
CallDismisser c;
return makeCall(args...); // [1]
}
只要 CallDismisser
的析构函数可以完成您需要做的所有事情,这就可以了。
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)...);
}
);
}
所以 wrapped_invoker
接受一个函数对象,并使它成为 return nothing
而不是 void
.
接下来,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
允许您保留 return 值并在需要时转换回 void
。您必须使用 {}
创建 holder<T>
才能使引用生命周期延长正常工作。添加一个 ctor 到 holder<T>
会破坏它。
holder<void>
默默地丢弃传递给它的任何东西。
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();
}
现在,holder<ReturnType>
不包含任何内容或 makeCall(args...)
的 return 值。
如果它什么都不包含,rv.get()
return 无效,return 对 ReturnValue
为 void
的函数无效是合法的。
基本上我们在做两个技巧。首先,我们要防止 makeCall
从 return 到 void
,其次,如果我们要从 return 到 void
,我们将丢弃 return 的值makeCall
有条件地。
overload
没有写在这里,但它是一个函数,它接受 1 个或多个函数对象(例如 lambdas)和 returns 它们的重载集。 std::overload
有一个提案,Whosebug 本身有无数的例子。
这是一些:
问题似乎出在 //Dismiss the call
上。
此代码不应存在。这就是我们拥有 RAII 的目的。以下代码确实有效,即使使用 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"; }
我有一个模板 class,它必须在调用其参数和 return 类型为通用的函数之前执行一些操作。
这是方法:
template <typename ReturnType, typename ...Args>
ReturnType function (Args ...args) {
// prepare for call
// ...
ReturnType rv = makeCall(args...); // [1]
// dismiss the call
// ...
return rv;
}
当然,当 ReturnType
不是 void
时编译正确。
当我在这种情况下使用它时:
function<void>(firstArg, secondArg);
编译器响应
error: return-statement with a value, in function returning 'void' [-fpermissive]
指向标有[1]的行。
除了将 -fpermissive
传递给编译器之外,还有其他解决方案吗?
我更愿意有一个独特的方法,因为我找到的可能的解决方案是使用 enable_if
和 is_same
.
提前致谢。
-- 更新--
这是一个完整的例子。我应该说我们的功能确实是 class 方法。
#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;
}
-- 更新--
不是大问题:使用 std::bind(&Caller::callImpl<ReturnType>, this, args)
。
这是我对通用的 C++11 兼容解决方案的尝试,您可以轻松地重复使用它。
让我们从创建一个简单的 type trait 开始,它将 void
转换为空结构。这不会引入任何代码重复。
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;
我们还需要一种方法来调用任意函数,将最终的 void
return 类型转换为 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)...);
}
用法:
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;
}
Matt Calabrese 提出了一项名为 "Regular Void" 的提案,可以解决此问题。 You can find it here: "P0146R1".
取决于您希望在行中完成什么
// dismiss the call
您或许可以使用:
template <typename ReturnType, typename ...Args>
ReturnType function (Args ...args) {
// prepare for call
// ...
CallDismisser c;
return makeCall(args...); // [1]
}
只要 CallDismisser
的析构函数可以完成您需要做的所有事情,这就可以了。
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)...);
}
);
}
所以 wrapped_invoker
接受一个函数对象,并使它成为 return nothing
而不是 void
.
接下来,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
允许您保留 return 值并在需要时转换回 void
。您必须使用 {}
创建 holder<T>
才能使引用生命周期延长正常工作。添加一个 ctor 到 holder<T>
会破坏它。
holder<void>
默默地丢弃传递给它的任何东西。
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();
}
现在,holder<ReturnType>
不包含任何内容或 makeCall(args...)
的 return 值。
如果它什么都不包含,rv.get()
return 无效,return 对 ReturnValue
为 void
的函数无效是合法的。
基本上我们在做两个技巧。首先,我们要防止 makeCall
从 return 到 void
,其次,如果我们要从 return 到 void
,我们将丢弃 return 的值makeCall
有条件地。
overload
没有写在这里,但它是一个函数,它接受 1 个或多个函数对象(例如 lambdas)和 returns 它们的重载集。 std::overload
有一个提案,Whosebug 本身有无数的例子。
这是一些:
问题似乎出在 //Dismiss the call
上。
此代码不应存在。这就是我们拥有 RAII 的目的。以下代码确实有效,即使使用 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"; }