Consider a typical absolute value function (where for the sake of argument the integral type of maximum size is long):
unsigned long abs(long input);
A naive implementation of this might look something like:
unsigned long abs(long input)
{
if (input >= 0)
{
// input is positive
// We know this is safe, because the maximum positive signed
// integer is always less than the maximum positive unsigned one
return static_cast<unsigned long>(input);
}
else
{
return static_cast<unsigned long>(-input); // ut oh...
}
}
This code triggers undefined behavior, because the negation of input
may overflow, and triggering signed integer overflow is undefined behavior. For instance, on 2s complement machines, the absolute value of std::numeric_limits<long>::min()
will be 1 greater than std::numeric_limits<long>::max()
.
What can a library author do to work around this problem?
One can cast to the unsigned variant first to avoid any undefined behavior:
In the above code, we invoke two well defined operations. Converting the signed integer to the unsigned one is well defined by N3485 4.7 [conv.integral]/2:
This basically says that when making the specific conversion of going from signed to unsigned, one can assume unsigned-style wraparound.
The negation of the unsigned integer is well defined by 5.3.1 [expr.unary.op]/8:
These two requirements effectively force implementations to operate like a 2s complement machine would, even if the underlying machine is a 1s complement or signed magnitude machine.
A generalized C++11 version that returns the unsigned version of an integral type:
This compiles on the Godbolt compiler explorer, with a test case showing that
gcc -O3 -fsanitize=undefined
finds no UB inuabs(std::numeric_limits<long>::min());
after constant-propagation, but does instd::abs()
.Further template stuff should be possible to make a version that would return the unsigned version of integral types, but return
T
for floating-point types, if you want a general-purpose replacement forstd::abs
.