我需要将一种std::chrono::duration
转换为另一种,但我需要知道何时无法进行此类转换,因为该值无法表示。
我没有在标准库中找到任何设施来检查这个。 cppreference page没有指定如果值超出范围会发生什么,只有从浮点到整数的转换可能是未定义的行为(在我的情况下,我需要从整数转换为整数)。
答案 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]
基于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是否在其他情况下有效。
请注意,我在很多情况下都没有测试过这两种解决方案中的任何一种。 很可能隐藏了其他可能发生溢出的情况。我对浮点运算不够精通,无法验证答案。