如何检查duration_cast中的溢出

时间:2017-06-19 16:48:26

标签: c++ integer-overflow chrono

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

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

2 个答案:

答案 0 :(得分:3)

没有一刀切解决方案,但适合多种用例的解决方案是使用基于double的{​​{1}}进行范围检查。也许是这样的:

duration

对我来说这是输出:

#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";
}

答案 1 :(得分:1)

constexpr添加到Howards答案后,我注意到以下内容

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


基于Howards的答案,我建议以下不受此问题困扰的

#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 Solution更改为以下内容:

#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<Duration>(s)更改为return duration_cast<Duration>(d)。 当两个Duration相同时,这可以让chrono处理问题, 但是我不确定d的duration_cast是否在其他情况下有效。

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