Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Given the following program:

#include <cmath>

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

gcc and clang disagree on whether this is ill-formed. Using gcc with libstdc++ the code builds without error or warning (see it live), while using clang with libc++ it generates the following error (see it live):

error: call to 'abs' is ambiguous
std::abs(0u) ;
^~~~~~~~

Which result is correct? Should abs(0u) be ambiguous or not?


MSalters points out an interesting related question: Template version of std::abs.

like image 504
Shafik Yaghmour Avatar asked Apr 20 '15 14:04

Shafik Yaghmour


1 Answers

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)
  {
    // ...
  }
  // ...
}
like image 77
Shafik Yaghmour Avatar answered Oct 20 '22 00:10

Shafik Yaghmour