【发布时间】:2017-06-19 16:48:26
【问题描述】:
我需要将一种 std::chrono::duration 转换为另一种,但我需要知道何时无法进行这种转换,因为该值无法表示。
我在标准库中没有找到任何工具来检查这一点。 cppreference page 没有指定如果值超出范围会发生什么,只是从浮点到整数的转换可能是未定义的行为(在我的情况下,我需要从整数转换为整数)。
【问题讨论】:
标签: c++ integer-overflow chrono
我需要将一种 std::chrono::duration 转换为另一种,但我需要知道何时无法进行这种转换,因为该值无法表示。
我在标准库中没有找到任何工具来检查这一点。 cppreference page 没有指定如果值超出范围会发生什么,只是从浮点到整数的转换可能是未定义的行为(在我的情况下,我需要从整数转换为整数)。
【问题讨论】:
标签: c++ integer-overflow chrono
没有一刀切解决方案,但是适合许多用例的解决方案是使用基于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
【讨论】:
在 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]
在霍华德回答的基础上,我建议以下不受此问题影响的方法。
#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 是否适用于其他情况。
请注意,我在很多情况下都没有测试过这两种解决方案中的任何一种。 很可能隐藏了其他可能溢出的情况。我对浮点运算不够精通,无法验证答案。
【讨论】:
duration_cast<Duration>(s) 否则尽管通过了比较检查,但 d 上的原始 duration_cast 可能会导致溢出。如果输入值 d 刚好大于目标持续时间的最大值,则转换为双精度可能会导致它向下舍入到该持续时间的最大值。 godbolt.org/z/HEY5Hz