We all know that C++ inherited unsafe semantics from C, but one would expect modern library features such as std::chrono to be carefully designed to avoid the old integer conversion bugs... right?

Not the case: it's very easy to silently cause truncation or overflow in correct-looking code, such as:

set_timeout(300s);

Try to get a compile-time diagnostic or at least a run-time error for this interface:
godbolt.org/z/K7fYGcx8h

This is some background info on std::chrono:
youtube.com/watch?v=P32hvk8b13

At 27:00 there's a hint that one could handle overflows by just using a safeint type:

duration<safe<int32_t>>

I don't think it's as simple as this slide hints: what are you going to do? Throw an exception at runtime? Saturate to the maximum representable value? Call abort()?

Constants that will certainly overflow should result in a compile-time error, just like in this case:

uint8_t byte{666};

sucks

@namark I just tried std::chrono::duration<boost::safe_numerics::safe<int32_t>> and immediately ran into trouble because duration_cast uses std::common_type, and oddly, Boost does not provide specializations for its safe types.

After defining a bunch of variants, I got the code to compile with exceptions. Then I tried switching to traps (since our embedded codebase is built with -fno-exceptions), and I hit another error cascade that I'm too tired to debug 😡

@namark Here's the code:
godbolt.org/z/GssW4G3ds

It won't compile in Godbolt because it needs Boost, and it won't compile with g++ or clang either, because of some weird error:

In template: type 'boost::safe_numerics::trap_exception' does not provide a call operator

@namark Oh wait, that's how they implemented the compile-time trap:

// emit compile time error if this is invoked.
struct trap_exception {
constexpr trap_exception() = default;
// error will occur on operator call.
// hopefully this will display arguments
};

Yeah, but it barfs a pageful of impenetrable template errors leading to this! I can't impose this horror on the whole team.

@codewiz you seemed to have a problem with the concept so that was an explanation and a proof of concept implementation. Pleasantly surprised it worked at all! If you care about it you should participate, cause as far as I know that is the most developed of any such concepts:
github.com/boostorg/safe_numer

common_type trait sounds like a misnomer, but I imagine chrono didn't have anything better to use. safe_numeric may provide alternatives on that front, though it will likely never be standardized in it's current form. It's much more likely for a more generic numeric library to get in, that would also handle arbitrary precision and provide the safe_numerics functionality as its subset, and probably not called "safe" cause that's not a very good name either.

Follow

@namark Sorry if it sounded like I was shooting the messenger. I find it upsetting that modern C++ comes with a sophisticated type-safe library for time conversion, but then it actually overflows just like K&R C.

I found an issue for adding common_type specializations for safe_numerics, but it was closed 2 years ago. I added a comment asking to re-evaluate:
github.com/boostorg/safe_numer

· · Web · 1 · 0 · 0

@codewiz the main point is that it's a nuanced issue out of scope for chrono. Any halfway solution would achieve very little and lower the quality of the library. The arithmetic bound checking is universally applicable, so it would be kind of silly to have something for durations that you couldn't use for other types. Also imposing any sort of compile-time, run-time or maintenance overhead any such checks would bring is nonsensical in a world where everyone in every other application of arithmetic is perfectly happy to just use a type that is assumed to be "large enough" to represent any value. Most just want to use a 64 bit int, and forget it can overflow, even at nanosecond precision, and some would vehemently argue that even on systems that don't have wide enough words, it should just be emulated in software.

@codewiz another thing you could do is define and use your own bound checked literals of different sizes. Here is an example for just plain integers
git.sr.ht/~namark/libsimple_su

If you stick with just one duration type throughout the codebase that would be all you need, otherwise if you want to mix and match duration of different granularity and size, your custom literals can return a wrapper type that would only be implicitly convertible to durations that they can fit in.

@codewiz keeping in mind that it's still a half measure, which won't save your from many potential pitfalls. For anything non trivial you'd still need to do a lot of mental (or pen and paper) calculations to make sure there is no overflow, so literals would be the least of your concerns on that front.

@namark Returning a wrapper type that's only implicitly convertible when it fits is possible only if each literal creates a different type with the value encoded in a non-type argument (like std::ratio<N,M>).

Perhaps this is doable with user-defined literals by declaring a templated operator"" which returns auto.

@codewiz yes, unless there are any explicit restriction in the standard that I'm not aware of, the operator can return any type based on the value of the literal. The literal value comes in as a variadic pack of chars.

Sign in to participate in the conversation
Mastodon

The social network of the future: No ads, no corporate surveillance, ethical design, and decentralization! Own your data with Mastodon!