我正在为未来的库编写一些泛型代码。我在一个模板函数中遇到了以下问题。考虑下面的代码:
template<class F>
auto foo(F &&f) {
auto result = std::forward<F>(f)(/*some args*/);
//do some generic stuff
return result;
}
它可以很好地工作,除非我向它传递一个返回void
的函数,如下所示:
foo([](){});
当然,现在我可以使用一些std::enable_if
魔术来检查返回类型,并对返回如下所示的void
的函数执行专门化:
template<class F, class = /*enable if stuff*/>
void foo(F &&f) {
std::forward<F>(f)(/*some args*/);
//do some generic stuff
}
但是,对于实际上逻辑上等价的函数,这将严重地重复代码。对于void
-returning和非void
-returning函数,这能以一种优雅的方式以通用的方式轻松完成吗?
编辑:在函数f()
和我想要做的泛型内容之间有数据依赖关系,所以我没有考虑这样的代码:
template<class F>
auto foo(F &&f) {
//do some generic stuff
return std::forward<F>(f)(/*some args*/);
}
发布于 2019-05-22 20:21:48
如果您可以将“一些泛型内容”放在bar
类的析构函数中(在安全的try/catch块中,如果您不确定这不会抛出异常,正如Drax所指出的那样),那么您可以简单地编写
template <typename F>
auto foo (F &&f)
{
bar b;
return std::forward<F>(f)(/*some args*/);
}
因此,编译器计算f(/*some args*/)
,执行b
的析构函数,并返回计算值(或不返回任何值)。
注意到return func();
是完全合法的,其中func()
是返回void
的函数。
发布于 2019-05-22 20:36:08
在某些地方,一些专业化是必要的。但这里的目标是避免专门化函数本身。但是,您可以专门化帮助器类。
使用带有-std=c++17
的gcc 9.1进行了测试。
#include <type_traits>
#include <iostream>
template<typename T>
struct return_value {
T val;
template<typename F, typename ...Args>
return_value(F &&f, Args && ...args)
: val{f(std::forward<Args>(args)...)}
{
}
T value() const
{
return val;
}
};
template<>
struct return_value<void> {
template<typename F, typename ...Args>
return_value(F &&f, Args && ...args)
{
f(std::forward<Args>(args)...);
}
void value() const
{
}
};
template<class F>
auto foo(F &&f)
{
return_value<decltype(std::declval<F &&>()(2, 4))> r{f, 2, 4};
// Something
return r.value();
}
int main()
{
foo( [](int a, int b) { return; });
std::cout << foo( [](int a, int b) { return a+b; }) << std::endl;
}
发布于 2019-05-22 22:12:58
在我看来,要做到这一点,最好的方法是实际改变调用可能返回void的函数的方式。基本上,我们将返回void
的类改为返回某种类类型Void
,也就是说,无论出于何种目的,都是一样的,没有用户会真正关心。
struct Void { };
我们所需要做的就是包装调用。下面的代码使用了C++17名称(std::invoke
和std::invoke_result_t
),但它们都可以在C++14中实现,不用太费事:
// normal case: R isn't void
template <typename F, typename... Args,
typename R = std::invoke_result_t<F, Args...>,
std::enable_if_t<!std::is_void<R>::value, int> = 0>
R invoke_void(F&& f, Args&&... args) {
return std::invoke(std::forward<F>(f), std::forward<Args>(args)...);
}
// special case: R is void
template <typename F, typename... Args,
typename R = std::invoke_result_t<F, Args...>,
std::enable_if_t<std::is_void<R>::value, int> = 0>
Void invoke_void(F&& f, Args&&... args) {
// just call it, since it doesn't return anything
std::invoke(std::forward<F>(f), std::forward<Args>(args)...);
// and return Void
return Void{};
}
这样做的好处是,你可以直接编写你想要写的代码,以你想要的方式开始:
template<class F>
auto foo(F &&f) {
auto result = invoke_void(std::forward<F>(f), /*some args*/);
//do some generic stuff
return result;
}
而且,您不必将所有逻辑都放在析构函数中,也不必通过专门化来复制所有逻辑。代价是foo([]{})
返回Void
而不是void
,这并不是很大的开销。
然后,如果Regular Void被采用,您所要做的就是用std::invoke
替换invoke_void
。
https://stackoverflow.com/questions/56256640
复制相似问题