2192. Validity and return type of std::abs(0u) is unclear

Section: 29.9 [c.math] Status: C++17 Submitter: Daniel Krügler Opened: 2012-10-02 Last modified: 2017-07-30

Priority: 2

View all other issues in [c.math].

View all issues with C++17 status.

Discussion:

In C++03 the following two programs are invalid:

  1. #include <cmath>
    
    int main() {
      std::abs(0u);
    }
    
  2. #include <cstdlib>
    
    int main() {
      std::abs(0u);
    }
    

because none of the std::abs() overloads is a best match.

In C++11 the additional "sufficient overload" rule from 29.9 [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:

  1. 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 29.9 [c.math] p11 (Note that the current resolution of LWG 2086 doesn't fix this problem).

  2. Any translation unit including both <cmath> and <cstdlib> 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: In contrast to all other floating-point functions explicitly listed in sub-clause 29.9 [c.math], the abs overloads have a special and well-defined meaning for signed integers and thus have explicit overloads returning a signed integral type. I also believe that there is no problem accepting that std::fabs(0u) is well-defined with return type double, because the leading 'f' clearly signals that we have a floating point function here. But the expected return type of std::abs(0u) seems less than clear to me. A very reasonable answer could be that this has the same type as its argument type, alternatively it could be a reasonably chosen signed integer type, or a floating point type. 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 <math.h> and <complex.h>, so cannot be applied to the abs functions (but to the fabs functions!).

Selecting a signed integer return type for unsigned input values can also problematic: The directly corresponding signed integer type would give half of the possible argument values an implementation-defined result value. Choosing the first signed integer value that can represent all positive values would solve this problem for unsigned int, but there would be no clear answer for the input type std::uintmax_t.

Based on this it seems to me that the C++03 state in regard to unsigned integer values was the better situation, alerting the user that this code is ambigious at the moment (This might be change with different core-language rules as described in N3387).

[2013-04-20, Bristol]

Resolution: leave as new and bring it back in Chicago.

[2013-09 Chicago]

This issue also relates to LWG 2294

STL: these two issues should be bundled

Stefanus: do what Pete says, and add overloads for unsigned to return directly

STL: agree Consensus that this is an issue

Walter: motion to move to Open

STL: no wording for 2294

Stefanus: move to open and note the 2 issues are related and should be moved together

Stefanus: add and define unsigned versions of abs()

[2014-02-03 Howard comments]

Defining abs() for unsigned integers is a bad idea. Doing so would turn compile time errors into run time errors, 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)
  {
    // ...
  }
  // ...
}

std::abs(expr) is often used to ask: Are these two numbers sufficiently close? When the assumption is that the two numbers are signed (either signed integral, or floating point), the logic is sound. But when the same logic is accidentally used with an arithmetic type not capable of representing negative numbers, and especially if unsigned overflow will silently happen, then the logic is no longer correct:

auto i = analyze(20u, 21u);  // Today a compile time error
    // But with abs(unsigned) becomes a run time error

This is not idle speculation. Search the net for "abs unsigned" here or here.

In C++11, chrono durations and time_points are allowed to be based on unsigned integers. Taking the absolute value of the difference of two such time_points would be easy to accidentally do (say in code templated on time_points), and would certainly be a logic bug, caught at compile time unless we provide the error prone abs(unsigned).

[2015-02, Cologne]

GR: Do we want to make the changes to both <cmath> and <cstdlib>?
AM: I think so; we should provide consistent overloads.
GR: Then we're imposing restrictions on what users put in the global namespace.
AM: I'm not so worried about that. Users already know not to use C library names.
VV: So what are we going to do about unsigned integers? AM: We will say that they are ill-formed.
AM: Does anyone volunteer to send updated wording to Daniel? GR, can you do it? GR: Sure.
GR: To clarify: we want to make unsigned types ill-formed?
AM: With promotion rank at least unsigned int.
GR: And NL suggests to just list those types.

Conclusion: Merge the resolution into a single issue.

Previous resolution from Daniel [SUPERSEDED]:

This wording is relative to N3376.

  1. Change 29.9 [c.math] p11 as indicated:

    -11- Moreover, except for the abs functions, there shall be additional overloads sufficient to ensure:

    […]

[2015-03-03, Geoffrey Romer provides improved wording]

In the following I've drafted combined wording to resolve LWG 2192 and 2294. Note that the first two paragraphs are taken verbatim from the P/R of LWG 2294, but the third is newly drafted:

[2015-05-05 Lenexa: Howard to draft updated wording]

[2015-09-11: Howard updated wording]

[2015-10, Kona Saturday afternoon]

HH: abs() for unsigned types is really dangerous. People often use abs(x - y), which would be a disaster.

TK: That's why you need a two-argument abs_diff(x, y), especially for unsigned types.

JW: Lawrence has a proposal for abs_diff in the mailing.

STL: As an alternative to considering promotions, I would just ban all unsigned types, even unsigned char.

STL: Does the PR change any implementation? Is the final paragraph just a consequence?

HH: It's a consequence. It could just be a note.

VV: Ship it as is.

STL: Editorial: capitalize the first letter in the Note.

Move to Tentatively ready.

Proposed resolution:

This wording is relative to N4527.

  1. Insert the following new paragraphs after 29.9 [c.math] p7:

    -6- In addition to the int versions of certain math functions in <cstdlib>, C++ adds long and long long overloaded versions of these functions, with the same semantics.

    -7- The added signatures are:

    long abs(long); // labs()
    long long abs(long long); // llabs()
    ldiv_t div(long, long); // ldiv()
    lldiv_t div(long long, long long); // lldiv()
    

    -?- To avoid ambiguities, C++ also adds the following overloads of abs() to <cstdlib>, with the semantics defined in <cmath>:

    float abs(float);
    double abs(double);
    long double abs(long double);
    

    -?- To avoid ambiguities, C++ also adds the following overloads of abs() to <cmath>, with the semantics defined in <cstdlib>:

    int abs(int);
    long abs(long);
    long long abs(long long);
    

    -?- If abs() is called with an argument of type X for which is_unsigned<X>::value is true and if X cannot be converted to int by integral promotion (7.6 [conv.prom]), the program is ill-formed. [Note: arguments that can be promoted to int are permitted for compatibility with C. — end note]