Return type of std::abs(short)

252 views
Skip to first unread message

Jörn Heusipp

unread,
Jun 6, 2016, 1:42:54 PM6/6/16
to ISO C++ Standard - Discussion

Hello!

This is my first post in the context of C++ standardization, so I may be missing some obvious things here.

I have found what I would consider a language specification defect. https://isocpp.org/std/submit-issue directed me to post here (std-discussion) first.

Consider this perfectly fine and working C++98 program:

#include <cmath>
#include <cstdlib>
int main() {
 
return std::abs(static_cast<short>(23)) % 42;
}

This compiles with all C++98 compilers. In the std::abs(short) call, short gets promoted to int and std::abs(int) is called.

C++11 added, amongst others, the following wording (quoting current C++17 working draft, but the wording had not changed):

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/n4582.pdf P.1083 §26.9.15.2 [c.math] "Otherwise,
if any argument of arithmetic type corresponding to a double argument has type double or an
integer type, then all arguments of arithmetic type corresponding to double arguments are
effectively cast to double."

C++17 draft adds another paragraph:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/n4582.pdf P.1080 §26.9.10 [c.math] "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 (4.5), the program is ill-formed. [ Note: Arguments that
can be promoted to int are permitted for compatibility with C. — end note ]"

I find it somewhat confusing and probably even contradictory to on the one hand specify abs() in terms of integral promotion in §26.9.10 and on the other hand demand all integral types to be converted to double in §26.9.15.2.

Most compilers I tested appear to not consider §26.9.15.2 for std::abs and compile the code successfully (tested on MSVS 2010-2015, GCC 4.1-4-4, clang 3.0-3.7, all with their own standard library implementation, and in all supported C++ versions (-std=c++98,c++11,c++14,c++1z).
GCC 6.x (tested with 6.1.1 on Debian testing) (and GCC 4.5-5.3 with std::abs but not with ::abs) however implement §26.9.15.2 and fail to compile (again in all std versions, even with -std=c++98 (which is certainly a GCC bug in my opinion)):

manx@vmdebiantesting:~/tmp$ cat test.cpp
#include <cmath>
#include <cstdlib>
int main() {
 
return std::abs(static_cast<short>(23)) % 42;
}
manx@vmdebiantesting
:~/tmp$ g++-6 -std=c++1z -O3 -Wall -Wextra test.cpp
test
.cpp: In function int main()’:
test
.cpp:4:42: error: invalid operands of types __gnu_cxx::__enable_if<true, double>::__type {aka double}’ and int to binary operator%’
 
return std::abs(static_cast<short>(23)) % 42;
         
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~
manx@vmdebiantesting
:~/tmp$

As far as I read the standard, GCC's interpretation appears to be valid.
However, as can be seen here, this breaks existing code.

I do acknowledge the reason for the wording and semantics demanded by §26.9.15.2, i.e. being able to call math functions with integral types or with partly floating point types and partly integral types. Converting integral types to double certainly makes sense here for all the other floating point math functions.
However, abs() is special. abs() has overloads for the 3 wider integral types which return integral types. abs() originates in the C standard in stdlib.h and had originally been specified for integral types only. Calling it in C with a short argument returns an int. Calling std::abs(short) in C++98 also returns an int. Calling std::abs(short) in C++11 and later with GCC's interpretation suddenly returns a double.

I know of multiple older code bases (https://lib.openmpt.org/ [1], https://www.libreoffice.org/ [2], http://kicad-pcb.org/ [3] and my own private code base as well) that already got bitten by this particular and/or related problems.
How the standard is meant to be interpreted in this case is not clear. If GCC's interpretation is considered to be valid or even the only correct one, I very much consider this a language defect as it clearly breaks existing and working code.
In its current (since C++11 / GCC) state, std::abs is also practically unusable in generic code because std::abs(std::int32_t) returns int or long and std::abs(std::int16_t) returns double, which is rather unintuitive and very confusing.
With GCC 6, ::abs() (i.e. C abs()) is even unusable in inline functions or macros included via C headers:

manx@vmdebiantesting:~/tmp$ cat somelib.h
#ifndef SOMELIB_H
#define SOMELIB_H
#include <math.h>
#include <stdlib.h>
#ifdef __cplusplus
extern "C" {
#endif
static inline short foo(short x) {
 
return abs(x) % 42;
}
#ifdef __cplusplus
}
#endif
#endif
manx@vmdebiantesting
:~/tmp$ cat test2.cpp
#include "somelib.h"
int main() {
 
return foo(23);
}
manx@vmdebiantesting
:~/tmp$ cat test3.c
#include "somelib.h"
int main() {
 
return foo(23);
}
manx@vmdebiantesting
:~/tmp$ g++-6 -std=c++1z -O3 -Wall -Wextra test2.cpp
In file included from test2.cpp:1:0:
somelib
.h: In function short int foo(short int)’:
somelib
.h:9:16: error: invalid operands of types __gnu_cxx::__enable_if<true, double>::__type {aka double}’ and int to binary operator%’
 
return abs(x) % 42;
         
~~~~~~~^~~~
manx@vmdebiantesting
:~/tmp$ gcc-6 -std=c99 -O3 -Wall -Wextra test3.c
manx@vmdebiantesting
:~/tmp$

With the increasing popularity of single-header C libraries like https://github.com/nothings/stb/ , this will affect probably even more code in the future and/or force C programmers to work around bugs in potentially future versions of the standard of another language (C++) which generally aims to be somewhat compatible with C (and had been in the past in this particular aspect).
Additionally, I fail to see a reason why this change in semantics of std::abs(short) could have been intentional in the first place.

Based on the assumption that my analysis is correct, in my opinion, the standard should at the very very least be clarified about what the semantics and return type of std::abs(short) should be (as apparently GCC's interpretation differs from MSVC's and clang's). If the standard demands ::abs and std::abs to behave identically (which I did not investigate thoroughly), it seems unavoidable to revert to C++98 (with addition of long long) semantics for std::abs in order to keep (or restore) compatibility with C (Note: We are not talking about core language features here where there are certainly valid points to not supporting every new C language feature in C++, but instead about a library API incompatibility. Compatibility is way easier to achieve there.).

Note, the situation for signed char is the same as the situation for short.

Note, §26.9.15.2 totally makes sense for std::fabs but not for std::abs, thus a possible solution may be to explicitly exempt std::abs in that paragraph. Another possible solution might be to explicitly demand integral promotion for narrower integral types somewhere near §26.9.10 for std::abs (i.e. by specifying overloads for signed char and short which cast to int).

Note, in a more complicated expression, where std::abs(short) is called somewhere near the leafs of the AST and further calculations are performed on the result without any explicit casting, the change in behaviour can cause potentially almost whole expression to be evaluated as double instead of as int. If the user is unlucky, even without any compiler warning. Even the computed value of the whole expression might change due to rounding/truncation. Running such code on a platform without an FPU can, in addition to the semantic problems, also lead to disastrous performance regressions.

Note, further discussions related to this issue are also available on the net at [4].

Note, as far as I can see, the proposed or implemented solutions for https://cplusplus.github.io/LWG/lwg-active.html#2294 , https://cplusplus.github.io/LWG/lwg-defects.html#2192 and/or https://cplusplus.github.io/LWG/lwg-defects.html#2086 will not resolve this issue.


Questions:

Was this change in behaviour intentional?

If so, why?

Must/should/may/must-not ::abs() and std::abs() behave identically?

Should it be considered a bug in the C++ standard?

Should it be considered as a bug in GCC's libstdc++?

Should I proceed reporting this issue as outlined in https://isocpp.org/std/submit-issue ?

If so, are there any opinions on how exactly the issue should be resolved?


Regards,
Jörn


Footnotes:

[1]
https://source.openmpt.org/browse/openmpt/trunk/OpenMPT/soundlib/tuning.cpp?op=revision&rev=6295&peg=6295 (please note that the commit message and comment are not accuratly reflecting the issue here, this was written before a more thorough analysis had been done)
https://source.openmpt.org/browse/openmpt/trunk/OpenMPT/soundlib/tuning.cpp?op=diff&rev=6295&peg=6295 (please note that the commit message and comment are not accuratly reflecting the issue here, this was written before a more thorough analysis had been done)
https://source.openmpt.org/browse/openmpt?manualorder=1&op=comp&compare[0]=%2Ftrunk%2FOpenMPT%2Fcommon%2Fmisc_util.h&compare_rev[0]=6298&compare[1]=%2Ftrunk%2FOpenMPT%2Fcommon%2Fmisc_util.h&compare_rev[1]=6313&comparesubmit=Compare+Paths

[2]
https://gerrit.libreoffice.org/#/c/22050/

[3]
These are more related to issue 2192 and 2086 then to this particular problem, but listed here in order to provide a better overview.
https://lists.launchpad.net/kicad-developers/msg21674.html
https://bazaar.launchpad.net/~kicad-product-committers/kicad/product/revision/5831
https://bazaar.launchpad.net/~kicad-product-committers/kicad/product/revision/6350

[4]
https://stackoverflow.com/questions/8226705/g-abs-on-a-short-int-appears-to-turn-it-into-a-double
https://gcc.gnu.org/ml/libstdc++/2016-02/msg00001.html

Greg Marr

unread,
Jun 6, 2016, 4:26:01 PM6/6/16
to ISO C++ Standard - Discussion
On Monday, June 6, 2016 at 1:42:54 PM UTC-4, Jörn Heusipp wrote:
I have found what I would consider a language specification defect. https://isocpp.org/std/submit-issue directed me to post here (std-discussion) first.

Consider this perfectly fine and working C++98 program:

This compiles with all C++98 compilers. In the std::abs(short) call, short gets promoted to int and std::abs(int) is called.

C++11 added, amongst others, the following wording (quoting current C++17 working draft, but the wording had not changed):

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/n4582.pdf P.1083 §26.9.15.2 [c.math] "Otherwise,
if any argument of arithmetic type corresponding to a double argument has type double or an
integer type, then all arguments of arithmetic type corresponding to double arguments are
effectively cast to double."

C++17 draft adds another paragraph:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/n4582.pdf P.1080 §26.9.10 [c.math] "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 (4.5), the program is ill-formed. [ Note: Arguments that
can be promoted to int are permitted for compatibility with C. — end note ]"

I find it somewhat confusing and probably even contradictory to on the one hand specify abs() in terms of integral promotion in §26.9.10 and on the other hand demand all integral types to be converted to double in §26.9.15.2.

26.9.15 comes immediately after 26.9.14, and appears to apply only to the functions in 26.9.14, or at most 26.9.11 and above.

26.9.14 "... Each function is overloaded for the three floating-point types, as follows:"
26.9.15 "Moreover, there shall be additional overloads sufficient to ensure: ..."

My reading is that 26.9.10 and 26.9.15 do not apply to the same functions.  The purpose
of 26.9.15 seems to be to describe how to deal with things such as "isgreater(1.0, 1.0f);"
or "isgreater(1, 10.0f);" where there are multiple arguments of different types.

GCC 6.x (tested with 6.1.1 on Debian testing) (and GCC 4.5-5.3 with std::abs but not with ::abs) however implement §26.9.15.2 and fail to compile (again in all std versions, even with -std=c++98 (which is certainly a GCC bug in my opinion)):
As far as I read the standard, GCC's interpretation appears to be valid.

I don't see anything in the standard that says that this is required.

Greg Marr

unread,
Jun 6, 2016, 4:35:55 PM6/6/16
to ISO C++ Standard - Discussion
Well, now that I've looked at the defect reports, it does seem that without 26.9.8-10,
that 26.9.15 could be read as applying to abs and div, but with those added sections,
it makes sense that if you just read 26.9 as a whole, without the historical context of,
what it used to be, as I did, that they should be separate.

Given that, I continue to think 26.9.11 through 26.9.15 should be read as completely
separate from 26.9.6 through 26.9.10.

Jörn Heusipp

unread,
Jun 6, 2016, 5:08:11 PM6/6/16
to ISO C++ Standard - Discussion


Am Montag, 6. Juni 2016 22:35:55 UTC+2 schrieb Greg Marr:
On Monday, June 6, 2016 at 4:26:01 PM UTC-4, Greg Marr wrote:
On Monday, June 6, 2016 at 1:42:54 PM UTC-4, Jörn Heusipp wrote:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/n4582.pdf P.1083 §26.9.15.2 [c.math] "Otherwise,
if any argument of arithmetic type corresponding to a double argument has type double or an
integer type, then all arguments of arithmetic type corresponding to double arguments are
effectively cast to double."
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/n4582.pdf P.1080 §26.9.10 [c.math] "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 (4.5), the program is ill-formed. [ Note: Arguments that
can be promoted to int are permitted for compatibility with C. — end note ]"

I find it somewhat confusing and probably even contradictory to on the one hand specify abs() in terms of integral promotion in §26.9.10 and on the other hand demand all integral types to be converted to double in §26.9.15.2.

26.9.15 comes immediately after 26.9.14, and appears to apply only to the functions in 26.9.14, or at most 26.9.11 and above.

26.9.14 "... Each function is overloaded for the three floating-point types, as follows:"
26.9.15 "Moreover, there shall be additional overloads sufficient to ensure: ..."

My reading is that 26.9.10 and 26.9.15 do not apply to the same functions.  The purpose
of 26.9.15 seems to be to describe how to deal with things such as "isgreater(1.0, 1.0f);"
or "isgreater(1, 10.0f);" where there are multiple arguments of different types.

Well, now that I've looked at the defect reports, it does seem that without 26.9.8-10,
that 26.9.15 could be read as applying to abs and div, but with those added sections,
it makes sense that if you just read 26.9 as a whole, without the historical context of,
what it used to be, as I did, that they should be separate.

Given that, I continue to think 26.9.11 through 26.9.15 should be read as completely
separate from 26.9.6 through 26.9.10.
 
I cant follow you here. Reading 26.9.11 through 26.9.15 as one block would result in just this std::abs(short) -> double overload that is causing my issue here, because abs(double) is actually listed in 26.9.12.
I'm fine with 26.9.6 through 26.9.10. The problem arise from abs() being listed in 26.9.12 and extended by 26.9.15, as far as I can see.

Greg Marr

unread,
Jun 8, 2016, 10:47:13 AM6/8/16
to ISO C++ Standard - Discussion
On Monday, June 6, 2016 at 5:08:11 PM UTC-4, Jörn Heusipp wrote:
I cant follow you here. Reading 26.9.11 through 26.9.15 as one block would result in just this std::abs(short) -> double overload that is causing my issue here, because abs(double) is actually listed in 26.9.12.
I'm fine with 26.9.6 through 26.9.10. The problem arise from abs() being listed in 26.9.12 and extended by 26.9.15, as far as I can see.

Ugh, you're right.  In my initial message, I was thinking that 26.9.15 only applied to 26.9.14,
and it's not in there.  When in my second message I then expanded the effect of 26.9.15 to
include 26.9.11, I didn't check the functions listed in 26.9.12.  It is only abs() that is affected
this way, as div() is not in 26.9.12.

Jörn Heusipp

unread,
Jun 13, 2016, 12:40:27 PM6/13/16
to ISO C++ Standard - Discussion

So, I guess I should proceed to report a proper Standard Library issue, right?

Reply all
Reply to author
Forward
0 new messages