【问题标题】:How to check for overflow in duration_cast如何检查 duration_cast 中的溢出
【发布时间】:2017-06-19 16:48:26
【问题描述】:

我需要将一种 std::chrono::duration 转换为另一种,但我需要知道何时无法进行这种转换,因为该值无法表示。

我在标准库中没有找到任何工具来检查这一点。 cppreference page 没有指定如果值超出范围会发生什么,只是从浮点到整数的转换可能是未定义的行为(在我的情况下,我需要从整数转换为整数)。

【问题讨论】:

    标签: c++ integer-overflow chrono


    【解决方案1】:

    没有一刀切解决方案,但是适合许多用例的解决方案是使用基于doubleduration 进行范围检查。可能是这样的:

    #include <chrono>
    #include <iostream>
    #include <stdexcept>
    
    template <class Duration, class Rep, class Period>
    Duration
    checked_convert(std::chrono::duration<Rep, Period> d)
    {
        using namespace std::chrono;
        using S = duration<double, typename Duration::period>;
        constexpr S m = Duration::min();
        constexpr S M = Duration::max();
        S s = d;
        if (s < m || s > M)
            throw std::overflow_error("checked_convert");
        return duration_cast<Duration>(s);
    }
    
    int
    main()
    {
        using namespace std::chrono;
        std::cout << checked_convert<nanoseconds>(10'000h).count() << "ns\n";
        std::cout << checked_convert<nanoseconds>(10'000'000h).count() << "ns\n";
    }
    

    对我来说这个输出:

    36000000000000000ns
    libc++abi.dylib: terminating with uncaught exception of type  std::overflow_error: checked_convert
    

    【讨论】:

      【解决方案2】:

      在 Howards 回答中添加 constexpr 后,我注意到以下内容

      static_assert(checked_convert<nanoseconds>(nanoseconds::max()) == nanoseconds::max());
      

      导致编译错误

      <source>: In function 'int main()':
      
      <source>:23:68: error: non-constant condition for static assertion
      
         23 |     static_assert(checked_convert<nanoseconds>(nanoseconds::max()) == nanoseconds::max());
      
            |                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~
      
      <source>:23:66:   in 'constexpr' expansion of 'checked_convert<std::chrono::duration<long int, std::ratio<1, 1000000000> >, long int, std::ratio<1, 1000000000> >(std::chrono::duration<long int, std::ratio<1, 1000000000> >::max())'
      
      <source>:16:35:   in 'constexpr' expansion of 'std::chrono::duration_cast<std::chrono::duration<long int, std::ratio<1, 1000000000> >, double, std::ratio<1, 1000000000> >(s)'
      
      /opt/compiler-explorer/gcc-9.2.0/include/c++/9.2.0/chrono:200:21:   in 'constexpr' expansion of 'std::chrono::__duration_cast_impl<std::chrono::duration<long int, std::ratio<1, 1000000000> >, std::ratio<1>, double, true, true>::__cast<double, std::ratio<1, 1000000000> >((* & __d))'
      
      <source>:23:68: error: overflow in constant expression [-fpermissive]
      

      https://godbolt.org/z/2bgPPM


      在霍华德回答的基础上,我建议以下不受此问题影响的方法。

      #include <chrono>
      #include <iostream>
      #include <stdexcept>
      #include <type_traits>
      
      template<class Duration, class Rep, class Period>
      constexpr auto checked_convert(std::chrono::duration<Rep, Period> d)
        -> std::enable_if_t<!std::is_same_v<Duration, decltype(d)>, Duration> {
          using namespace std::chrono;
          using S       = duration<double, typename Duration::period>;
          constexpr S m = Duration::min();
          constexpr S M = Duration::max();
          S           s = d;
          if(s < m || s > M) { throw std::overflow_error("checked_convert"); }
          return duration_cast<Duration>(s);
      }
      
      template<class Duration>
      constexpr Duration checked_convert(Duration d) {
          return d;
      }
      
      int main() {
          using namespace std::chrono;
          static_assert(checked_convert<nanoseconds>(nanoseconds::max()) == nanoseconds::max());
          std::cout << checked_convert<nanoseconds>(nanoseconds::max()).count() << "ns\n";
          std::cout << checked_convert<nanoseconds>(10'000h).count() << "ns\n";
          std::cout << checked_convert<nanoseconds>(10'000'000h).count() << "ns\n";
      }
      

      第二个重载确保当 from 和 to Duration 是相同类型时不会发生转换。


      我摆脱 UB 的另一种方法是将 Howars 解决方案更改为以下:

      #include <chrono>
      #include <iostream>
      #include <stdexcept>
      
      template <class Duration, class Rep, class Period>
      constexpr Duration
      checked_convert(std::chrono::duration<Rep, Period> d)
      {
          using namespace std::chrono;
          using S = duration<double, typename Duration::period>;
          constexpr S m = Duration::min();
          constexpr S M = Duration::max();
          S s = d;
          if (s < m || s > M)
              throw std::overflow_error("checked_convert");
          return duration_cast<Duration>(d);
      }
      
      int
      main()
      {
          using namespace std::chrono;
          static_assert(checked_convert<nanoseconds>(nanoseconds::max()) == nanoseconds::max());
          std::cout << checked_convert<nanoseconds>(10'000h).count() << "ns\n";
          std::cout << checked_convert<nanoseconds>(10'000'000h).count() << "ns\n";
      }
      

      注意从return duration_cast&lt;Duration&gt;(s)return duration_cast&lt;Duration&gt;(d) 的变化。 这让 chrono 在两个 Duration 相同时处理问题, 但我不确定带 d 的 duration_cast 是否适用于其他情况。

      请注意,我在很多情况下都没有测试过这两种解决方案中的任何一种。 很可能隐藏了其他可能溢出的情况。我对浮点运算不够精通,无法验证答案。

      【讨论】:

      • 对于它们不同的情况,我认为您想返回 duration_cast&lt;Duration&gt;(s) 否则尽管通过了比较检查,但 d 上的原始 duration_cast 可能会导致溢出。如果输入值 d 刚好大于目标持续时间的最大值,则转换为双精度可能会导致它向下舍入到该持续时间的最大值。 godbolt.org/z/HEY5Hz
      猜你喜欢
      • 1970-01-01
      • 2011-01-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-08-15
      相关资源
      最近更新 更多