ARTICLE AD BOX
Is there a way to easily automate this checking in modern C++, at least for debug builds?
Yes.
The <chrono> durations (and time_points) can use class types for the underlying representation as opposed to integral (or floating point) types. For example they can use a "safe int" type that turns overflow into a noisy run-time error.
One free, open-source, header-only example of such a class is the bbi integer library.1 This library can be used to easily define your own custom chrono types that will terminate on overflow.
For example:
#include "bbi.h" #include <chrono> #include <iostream> namespace mychrono { using namespace bbi::term; using nanoseconds = std::chrono::duration<i64, std::nano>; using microseconds = std::chrono::duration<i64, std::micro>; using milliseconds = std::chrono::duration<i64, std::milli>; using seconds = std::chrono::duration<i64>; using minutes = std::chrono::duration<i32, std::ratio<60>>; using hours = std::chrono::duration<i32, std::ratio<3'600>>; using days = std::chrono::duration<i32, std::ratio<86'400>>; using weeks = std::chrono::duration<i32, std::ratio<604'800>>; using months = std::chrono::duration<i32, std::ratio<2'629'746>>; using years = std::chrono::duration<i32, std::ratio<31'556'952>>; } // namespace mychrono int main() { using namespace mychrono; years y{300}; nanoseconds x{300}; std::cout << (y > x) << '\n'; }This requires C++20 or later, and on recent versions of MSVC, gcc and clang, will output the message:
Z<Signed, 64, Terminate>{300) * Z<Signed, 64, Terminate>{31556952000000000) overflowedand then call std::terminate.
Working demo for all three major platforms.
The only changes to the code are:
#include "bbi.h" Type aliases for the chrono types under your own namespace Change using namespace std::chrono to using namespace mychrono (or manage the namespace change however you prefer).Once you have debugged your code and want to return to the full efficiency of a release build, simply change:
using namespace bbi::term;to:
using namespace bbi::raw;This change redefines your custom chrono types to use built in signed integers of the indicated bit width for their representation.
If you would like to throw an exception on overflow instead of call std::terminate, then change:
using namespace bbi::term;to:
using namespace bbi::thrw;Then on overflow a std::overflow_error will be thrown with a what() message identical to the one printed out previously with terminate().
To experiment with saturated arithmetic change:
using namespace bbi::term;to:
using namespace bbi::sat;Now this program outputs the correct answer:
1300 years is greater than 300ns.
Another way to use this library to get the correct answer instead of overflow is to increase the range of nanoseconds to +/- far beyond the age of the universe. This can be done by having nanoseconds represented by a signed 128 bit type, instead of a 64 bit type. Change:
using nanoseconds = std::chrono::duration<i64, std::nano>;to:
using nanoseconds = std::chrono::duration<i128, std::nano>;Now this program outputs the correct answer:
1300 years is greater than 300ns.
You can no longer have a zero overhead release build with this setting as there is no portable 128 bit type (until we get a portable _BitInt(128)), but you can remove the overflow checks by changing:
using namespace bbi::term;to:
using namespace bbi::wrap;1 I am the main author of bbi. I am not seeking any financial benefit from this library. I consider helping people with C++ in general, and chrono in particular a hobby of mine.
