Is std::abs(0u) ill-formed?

Looks like libstdc++ is correct, this is not ill-formed, although we will see there are some doubts expressed over whether this is a defect in LWG active issue 2192.

The draft C++11 standard section 26.8 [c.math] paragraph 11 says:

Moreover, there shall be additional overloads sufficient to ensure:

and includes the following item:

  1. Otherwise, if any argument corresponding to a double parameter has type double or an integer type, then all arguments corresponding to
    double parameters are effectively cast to double.

and we can see this libstdc++ does indeed provide for this case:

template<typename _Tp>
inline typename __gnu_cxx::__enable_if<__is_integer<_Tp>::__value,
                                                  double>::__type
abs(_Tp __x)
{ return __builtin_fabs(__x); }

There is a also a gcc bug report std::abs (long long) resorts to std::abs (double) if llabs is absent, which questions if this implementation is correct and one response says:

[…]is fine per the Standard, any integer is supposed to
unconditionally become double. […]

The bug report eventually lead to LWG active issue 2192: Validity and return type of std::abs(0u) is unclear being filed which says amongst other things:

  1. In C++11 the additional “sufficient overload” rule from 26.8 [c.math]
    p11 (see also LWG 2086) can be read to be applicable to the std::abs()
    overloads as well, which can lead to the following possible
    conclusions:

The program

    #include <type_traits>
    #include <cmath>

    static_assert(std::is_same<decltype(std::abs(0u)), double>(), "Oops");

    int main() {
      std::abs(0u); // Calls std::abs(double)
    }

is required to be well-formed, because of sub-bullet 2 (“[..] or an
integer type [..]”) of 26.8 [c.math] p11 (Note that the current
resolution of LWG 2086 doesn’t fix this problem).

  1. Any translation unit including both and might be ill-formed because of two conflicting requirements for the return type
    of the overload std::abs(int).

It seems to me that at least the second outcome is not intended,
personally I think that both are unfortunate […] It should also be
noted, that the corresponding “generic type function” rule set from
C99/C1x in 7.25 p2+3 is restricted to the floating-point functions
from and , so cannot be applied to the abs
functions (but to the fabs functions!).

The question is whether this was intended to apply to abs as well. This could be a defect since there does not seem to a way to interpret the current wording to exclude abs.

So current wording indicates libstdc++ is conformant, it is not clear why libc++ has chosen their current implementation as it is. I can find no bug reports nor discussions involving this topic and the LWG issue does not mention diverging implementations.

The proposed solution would make std::abs(0u) ill-formed:

If abs() is called with an argument of unsigned integral type that
cannot be converted to int by integral promotion ([conv.prom]), the
program is ill-formed. [Note: arguments that can be promoted to int
are permitted for compatibility with C. — end note]

While some may question the notion of using abs with an unsigned type Howard Hinnant points out in the report that when using templates such consequences may not be apparent and provides an example:

[…]especially in C++ where we have templates, and the types involved
are not always apparent to the programmer at design time. For example,
consider:

template <class Int>
Int
analyze(Int x, Int y)
{
  // ...
  if (std::abs(x - y) < threshold)
  {
    // ...
  }
  // ...
}

Leave a Comment