【问题标题】:Avoiding Repetition For SFINAE Differentiating Between void and Non-void Return Types避免重复 SFINAE 区分无效和非无效返回类型
【发布时间】:2016-03-23 03:46:38
【问题描述】:

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

可以看出,函数返回值与否的情况是有区别的。在前一种情况下,必须存储值,打印经过的时间,并返回值;在后一种情况下,打印经过的时间后,无需进行任何其他操作。

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

    1234563尤其是身体的大部分重复。
  1. 上述答案绕过了该问题,方法是将经过的时间打印为调用某个经过的计时器类的析构函数的副产品。这是个好主意,但在更复杂的用途中会导致代码复杂(大量工作是在某个单独类的析构函数中完成的 - 这不是自然流程)。

有更好的方法吗?

【问题讨论】:

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


    【解决方案1】:

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

    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 获得批准会特别好 - 到那时我们甚至根本不需要特殊情况!

    【讨论】:

    • 也感谢您提供指向常规 void 的链接!
    【解决方案2】:

    您可以隔离调用和存储代码:

    template<class R>
    struct invoke_and_store_t {
      std::experimental::optional<R> ret;
      template<class F, class...Args>
      invoker_t&& operator()(F&& f, Args&&...args)&& {
        ret.emplace( std::forward<F>(f)(std::forward<Args>(args)...) );
        return std::move(*this);
      }
      R&& get()&&{ return std::move( *ret ) ); }
      template<class F>
      auto chain(F&& f)&&{
        return [r = std::move(*this).get(),f=std::move<F>(f)](auto&&...args)mutable
        {
          return std::move(f)(std::move(r), decltype(args)(args)...);
        };
      }
    };
    template<>
    struct invoke_and_store_t<void> {
      template<class F, class...Args>
      invoker_t&& operator()(F&& f, Args&&...args)&& {
        std::forward<F>(f)(std::forward<Args>(args)...);
        return std::move(*this);
      }
      void get()&&{}
      template<class F>
      auto chain(F&& f)&&{
        return [f=std::move<F>(f)](auto&&...args)mutable{
          return std::move(f)(decltype(args)(args)...);
        };
      }
    };
    template<class F, class...Args, class R=std::decay_t<std::result_of_t<F(Args...)>>>
    auto invoke_and_store(F&& f, Args&&...args) {
      return invoke_and_store_t<R>{}(std::forward<F>(f), std::forward<Arg>(args)...);
    }
    

    现在你的代码变成了:

    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&& res = invoke_and_store(
        std::forward<Fn>(fn), std::forward<Args>(args)...
      );
      const auto end = std::chrono::system_clock::now();
      std::cout << "elapsed " << (end - start).count() << std::endl;
      return std::move(res).get();
    }
    

    现在这两种情况具有相同的主体。我解决了将返回值(或不)存储到帮助器中的问题,从而使想要处理它的代码不必担心它。

    我还包括了chain,它接受一个函数对象并将前一个返回值作为第一个参数传递给它,或者不传递,这取决于前一个返回值是否为空。我发现这种模式在类 monad/functor 的代码中很常见。

    template<class A, class B>
    auto then( A&& a, B&& b ) {
      return [a = std::forward<A>(a), B=std::forward<B>(b)](auto&&...args)mutable{
        return
          invoke_and_store(std::move(a))
          .chain(std::move(b))(decltype(args)(args)...);
      };
    }
    

    then(a,b)(...) 调用a() 然后b(a(),...)a() 然后b(...) 取决于a() 返回的内容。

    【讨论】:

    • @AmiTavory 将其更改为使用 std::experimental::optional,可以将其替换为 boost::optional,因为我发现了异常安全问题。
    【解决方案3】:

    也许一些辅助结构可以解决问题?

    template <class T>
    struct enable_if_not_void: enable_if<!is_void<T>::value, T> { };
    

    及用法:

    template<class Fn, typename ...Args>
    auto time_it(Fn fn, Args &&... args) -> typename enable_if_not_void<typename std::result_of<Fn(Args &&...)>::type>::type {
      //...
    }
    

    【讨论】:

    • Fn(decltype(std::forward&lt;Args&gt;(args))...) -> Fn(Args...)
    • 不需要decltype(std::forward&lt;Args&gt;(args))...) 那只是Args&amp;&amp;...
    猜你喜欢
    • 2012-02-07
    • 2019-05-07
    • 1970-01-01
    • 2022-12-06
    • 2018-09-21
    • 2013-05-19
    • 1970-01-01
    • 2019-07-15
    • 2019-07-08
    相关资源
    最近更新 更多