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.