说我有一些包装函数,我想在其中进行一些设置,调用回调(并保存其结果),进行一些清理,然后返回回调返回的内容:
#include <functional>
#include <utility>
template<class F, typename... Args>
decltype(auto) wrap(F&& func, Args&... args) {
// Do some stuff before
auto result = std::invoke(std::forward<decltype(func)>(func),
std::forward<Args>(args)...);
// Do some stuff after
return result;
}
一个实际的例子是一个计时器实用程序函数,该函数返回函数调用的经过时间及其返回值(也许在一个元组中)。
这样的功能对于具有返回类型的可调用对象来说很好用
void foo() {
auto a = 1;
wrap([](auto a) { return 1; }, a);
}
但是对于具有void返回类型的可调用对象,在模板专门化期间,编译器会抱怨auto result
具有不完整的void类型:
void foo() {
auto a = 1;
wrap([](auto a) {}, a);
}
这当然很合理,因为while you can return void()
不能将其存储在变量中。
我希望wrap
为两种可调用对象工作。我尝试使用std::function
为wrap
给出两个签名:
template<class T, typename... Args> decltype(auto) wrap(std::function<T(Args...)>, Args&... args)
template<typename... Args> decltype(auto) wrap(std::function<void(Args...)>, Args&... args)
其中第一个将继续匹配具有非void
返回的可调用对象,但后者无法匹配具有返回类型void
的那些可调用对象。
有没有一种方法可以使返回类型void
和非{void
可调用的情况下的包装工作正常?
答案 0 :(得分:5)
我想解决这个问题的方法是使用regular void。嗯,我们实际上没有合适的常规void
,但是我们可以使自己的常规类型Void
类似于void
。然后提供invoke
周围的包装,以了解这一点。
最短的版本(该博客中有更多详细信息):
struct Void { };
template <typename F, typename ...Args,
typename Result = std::invoke_result_t<F, Args...>,
std::enable_if_t<!std::is_void_v<Result>, int> = 0>
Result invoke_void(F&& f, Args&& ...args) {
return std::invoke(std::forward<F>(f), std::forward<Args>(args)...);
}
// void case
template <typename F, typename ...Args,
typename Result = std::invoke_result_t<F, Args...>,
std::enable_if_t<std::is_void_v<Result>, int> = 0>
Void invoke_void(F&& f, Args&& ...args) {
std::invoke(std::forward<F>(f), std::forward<Args>(args)...);
return Void();
}
这使您可以使用原始代码执行以下操作:
template<class F, typename... Args>
decltype(auto) wrap(F&& func, Args&... args) {
// Do some stuff before
auto result = invoke_void(std::forward<decltype(func)>(func),
std::forward<Args>(args)...);
// Do some stuff after
return result;
}
即使func
返回void
,这仍然有效。
答案 1 :(得分:2)
如果“之后做一些事情”不使用result
变量,则可以通过使用喜欢的范围保护库来避免SFINAE。我在这里提供了一个非常幼稚的实现,但是您可以在网上找到更多精致的实现。
#include <functional>
template <typename F>
struct ScopeExit_impl {
F f;
ScopeExit_impl(F f) : f(std::move(f)) {}
~ScopeExit_impl() noexcept { f(); }
};
template <typename F>
ScopeExit_impl<F> ScopeExit(F &&f) {
return ScopeExit_impl<F>{std::forward<F>(f)};
}
template <class F, typename... Args>
decltype(auto) wrap(F &&func, Args &... args) {
// Do some stuff before
auto exit = ScopeExit([&]() {
// Do some stuff after
});
return std::invoke(std::forward<decltype(func)>(func),
std::forward<Args>(args)...);
}
int main() {
auto a = 1;
wrap([](auto a) { return 1; }, a);
wrap([](auto a) {}, a);
}