簡體   English   中英

如何檢查 duration_cast 中的溢出

[英]How to check for overflow in duration_cast

我需要將一種std::chrono::duration為另一種類型,但我需要知道何時無法進行此類轉換,因為該值無法表示。

我還沒有在標准庫中找到任何工具來檢查這一點。 cppreference 頁面沒有指定如果值超出范圍會發生什么,只有從浮點到整數的轉換可能是未定義的行為(在我的情況下,我需要從整數轉換為整數)。

沒有一刀切的解決方案,但是適合許多用例的解決方案是使用基於double精度的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";
}

對我來說,這個輸出:

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

在將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


基於霍華德的回答,我建議以下不受此問題影響的內容。

#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<Duration>(s)return duration_cast<Duration>(d) 這讓 chrono 在兩個 Duration 相同時處理問題,但我不確定帶有 d 的 duration_cast 是否對其他情況有效。

請注意,我尚未在許多情況下測試過這兩種解決方案中的任何一種。 很可能隱藏了其他可能發生溢出的情況。 我對浮點運算不夠精通,無法驗證答案。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM