I need to convert one kind of std::chrono::duration
to another kind but I need to know when such a conversion is not possible because the value would not be representable.
I have not found any facilities in the standard library to check this. The cppreference page does not specify what happens if the value is out of range, only that conversion from floating-point to integer may be undefined behavior (in my case I need to convert from integer to integer).
There is no one-size-fits-all solution, however a solution that fits many use cases is to use a double
-based duration
for the range checking. Maybe something like:
#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";
}
For me this outputs:
36000000000000000ns
libc++abi.dylib: terminating with uncaught exception of type std::overflow_error: checked_convert
After adding constexpr
to Howards answer I noticed that following
static_assert(checked_convert<nanoseconds>(nanoseconds::max()) == nanoseconds::max());
lead to a compiler error
<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
Building upon Howards answer I would suggest the following which does not suffer from this problem.
#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";
}
The second overload ensures that there is no conversion happening when the from and the to Duration are the the same type.
Another way I got rid of the UB was to change Howars Solution to following:
#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";
}
Notice the change from return duration_cast<Duration>(s)
to return duration_cast<Duration>(d)
.
This lets chrono handle the problem when the two Durations are the same,
but I´m not sure if the duration_cast with d is valid for the other cases.
Be aware that i have not tested any of this two solution for many cases. It is very likely that there are hiding other cases where there could be an overflow. I´m not versed enough in floating point arithmetic to verify the answer.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With