c++ - 避免重复 SFINAE 区分无效和非无效返回类型

标签 c++ c++11 void sfinae result-of

一些通用的代码操作函数,需要根据函数是否有返回值进行不同的操作。例如,从 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);
}   

可以看出,函数有无返回值的情况是有区别的。在前一种情况下,必须存储值,打印耗时,并返回值;在后一种情况下,打印耗时后,无需再做任何事情。

问题是如何处理这两种情况:

  1. 以上代码使用std::enable_ifis_void ,但是 is_void 的第一个(本身很麻烦)参数被重复作为 enable_if 的最后一个参数 - 这很麻烦并且 smells , 特别是重复 body 的大部分。

  2. 上述答案通过将耗时打印为调用某个经过的计时器类的析构函数的副产品来绕过该问题。这是个好主意,但在更复杂的用途中会导致代码复杂(大量工作是在某个单独类的析构函数中完成的 - 这不是自然流程)。

有更好的方法吗?

最佳答案

有时您只需要一个简单的标签类型:

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/

相关文章:

c++ - 如何仅强制类的智能指针实例?

c++ - 原子结构的统一初始化?

c++ - C++ 标准中是否有任何计划来解决初始化列表构造函数的不一致问题?

Ruby 替代 void 返回类型

c++ - 如何在同一解决方案中包含其他项目的头文件?

c++ - G++ -M32 -M64 在 Windows 上切换

c++ - 粗细的画线变白,OpenCv iOS 问题

c++ - 包含 iostream 会导致不同的二进制文件

c - C 存储中的 void 类型变量

c++ - (void)变量的意义