一些通用的代码操作函数,需要根据函数是否有返回值进行不同的操作。例如,从 this question 借用一个问题,假设我们需要编写一个 time_it
函数,它接受一个函数和一些参数,运行它,并打印耗时。下面的代码可以做到这一点:
#include <chrono>
#include <type_traits>
#include <cmath>
#include <iostream>
template<class Fn, typename ...Args>
auto time_it(Fn fn, Args &&...args) ->
typename std::enable_if<
!std::is_void<typename std::result_of<Fn(decltype(std::forward<Args>(args))...)>::type>::value,
typename std::result_of<Fn(decltype(std::forward<Args>(args))...)>::type>::type
{
const auto start = std::chrono::system_clock::now();
auto const res = fn(std::forward<Args>(args)...);
const auto end = std::chrono::system_clock::now();
std::cout << "elapsed " << (end - start).count() << std::endl;
return res;
}
template<class Fn, typename ...Args>
auto time_it(Fn fn, Args &&...args) ->
typename std::enable_if<
std::is_void<typename std::result_of<Fn(decltype(std::forward<Args>(args))...)>::type>::value,
void>::type
{
const auto start = std::chrono::system_clock::now();
fn(std::forward<Args>(args)...);
const auto end = std::chrono::system_clock::now();
std::cout << "elapsed " << (end - start).count() << std::endl;
}
int main()
{
time_it([](double x){return std::cos(x);}, 3.0);
time_it([](double x){}, 3.0);
}
可以看出,函数有无返回值的情况是有区别的。在前一种情况下,必须存储值,打印耗时,并返回值;在后一种情况下,打印耗时后,无需再做任何事情。
问题是如何处理这两种情况:
以上代码使用
std::enable_if
和is_void
,但是is_void
的第一个(本身很麻烦)参数被重复作为enable_if
的最后一个参数 - 这很麻烦并且 smells , 特别是重复 body 的大部分。上述答案通过将耗时打印为调用某个经过的计时器类的析构函数的副产品来绕过该问题。这是个好主意,但在更复杂的用途中会导致代码复杂(大量工作是在某个单独类的析构函数中完成的 - 这不是自然流程)。
有更好的方法吗?
最佳答案
有时您只需要一个简单的标签类型:
template <class > struct tag { };
您可以根据包装的结果类型调度您的time_it
:
template <class Fn, class... Args, class R = std::result_of_t<Fn&&(Args&&...)>>
R time_it(Fn fn, Args&&... args)
{
return time_it(tag<R>{}, fn, std::forward<Args>(args)...);
}
然后我们只对 void
和非 void
版本进行重载:
template <class R, class Fn, class... Args>
R time_it(tag<R>, Fn fn, Args&&... args)
{
const auto start = std::chrono::system_clock::now();
auto const res = fn(std::forward<Args>(args)...);
const auto end = std::chrono::system_clock::now();
std::cout << "elapsed " << (end - start).count() << std::endl;
return res;
}
template <class Fn, class... Args>
void time_it(tag<void>, Fn fn, Args&&... args)
{
const auto start = std::chrono::system_clock::now();
fn(std::forward<Args>(args)...);
const auto end = std::chrono::system_clock::now();
std::cout << "elapsed " << (end - start).count() << std::endl;
}
当然,如果regular void会特别好获得批准 - 到那时我们甚至根本不需要特殊情况!
关于c++ - 避免重复 SFINAE 区分无效和非无效返回类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36160102/