When the bit-width of int
(commonly 32) is wider than end, beg
(16), end - beg
is like (int)end - (int)beg
with an int
result - potentially negative.
To fix that, I have to cast the difference to uint16_t
:
// OP's approach
if (uint16_t(end - beg) >= zero) cout << "This branch is always taken.\n";
else cout << "This branch will never be taken.\n";
That is one way.
Another way to drive code to use unsigned
math:
// Suggested alternative
// if (uint16_t(end - beg) >= zero) ...
if (0u + end - beg >= zero) ...
This will cause unsigned math throughout 0u + end - beg >= zero
*1.
The trouble with casts is one of code maintenance. Should code later use uint32_t beg, end
, consider the now bug uint16_t(end - beg)
.
True that any local code change warrants a larger review, it is simply that gentle nudges to use unsigned
math typically cause less issues and less maintenance than casts. The nudge never narrows. A cast may widened or narrow.
Deeper
The core issues lies with using unsigned types narrower than int/unsigned
. When able, avoid this for non-array objects like beg, end, ...
. When obliged to use small unsigned types that may be narrower than unsigned
, take care to insure the desired unsigned/signed math is used on these objects.
From time-to-time, I wanted a uint_N_and_at_least_unsigned_width_t
type to avoid this very problem.
*1 Unless beg, end
later become a signed integer type wider than unsigned
. In which case, usually the wider signed math is preferred.