C++ implicit numeric type demoting - c++

Recently, I have noticed that C/C++ seems to be very permissible with numeric type conversion, as it implicitly casts a double to int.
Test:
Environment: cpp.sh, Standard C++ 14, Compilation warnings all set
Code:
int intForcingFunc(double d) {
return d; // this is allowed
}
int main() {
double d = 3.1415;
double result = intForcingFunc(d);
printf("intForcingFunc result = %f\n", result);
int localRes = d; // this is allowed
printf("Local result = %d\n", localRes);
int staticCastRes = static_cast<int>(d); // also allowed
printf("Static cast result = %d\n", staticCastRes);
}
No warnings are issues during compilation.
Documentation mentions tangentially the subject, but misses the exact case of the question:
C++ is a strong-typed language. Many conversions, specially those that imply a different interpretation of the value, require an explicit conversion, known in C++ as type-casting.
I have also tried in a managed language (C#) and all these cases are not allowed (as expected):
static int intForcingFunc(double d)
{
// Not legal: Cannot implicitly convert type 'double' to 'int'
// return d;
return Convert.ToInt32(d);
}
static void Main(string[] args)
{
double d = 3.1415;
double result = intForcingFunc(d);
Console.WriteLine("intForcingFunc result = " + result);
// Not legal: Cannot implicitly convert type 'double' to 'int'
// int localRes = d;
int localRes = (int)d;
Console.WriteLine("local result = " + result);
Console.ReadLine();
}
Why is this behavior allowed in a strong-typed language? In most cases, this is undesired behavior. One reason behind this seems to be lack of arithmetic overflow detection.

Unfortunately, this behavior is inherited from C, which notoriously "trusts the programmer" in these things.
The exact warning flag for implicit floating-point to integer conversions is -Wfloat-conversion, which is also enabled by -Wconversion. For some unknown reason, -Wall, -Wextra, and -pedantic (which cpp.sh provides) don't include these flags.
If you use Clang, you can give it -Weverything to enable literally all warnings. If you use GCC, you must explicitly enable -Wfloat-conversion or -Wconversion to get a warning when doing such conversions (among other useful flags you will want to enable).
If you want, you can turn it to an error with e.g. -Werror-conversion.
C++11 even introduced a whole new safer initialization syntax, known as uniform initialization, which you can use to get warnings for the implicit conversions in your example without enabling any compiler warnings:
int intForcingFunc(double d) {
return {d}; // warning: narrowing conversion of 'd' from 'double' to 'int' inside { }
}
int main() {
double d{3.1415}; // allowed
int localRes{d}; // warning: narrowing conversion of 'd' from 'double' to 'int' inside { }
}

You did not specify what compiler you are working with, but you probably have not, in fact, enabled all warnings. There's a story behind it, but the net effect is that g++ -Wall does not actually enable all warnings (not even close). Others (eg. clang++), in order to be drop-in replacement-compatible with g++ must do the same.
Here is a great post on setting strong warnings for g++: https://stackoverflow.com/a/9862800/1541330
If you are using clang++, things will be much easier for you: try using -Weverything. It does just what you expect (turns on every warning). You can add -Werror and the compiler will then treat any warnings that occur as compile-time errors. If you are now seeing warnings (errors) that you want to suppress, just add -Wno-<warning-name> to your command (eg. -Wno-c++98-compat).
Now the compiler will warn you whenever an implicit narrowing conversion (conversion with possible data loss that you didn't explicitly ask for) occurs. In cases where you want a narrowing conversion to occur, you must use an appropriate cast, eg:
int intForcingFunc(double d) {
return static_cast<int>(d); //cast is now required
}

Related

Expecting warnings when casting enums to integers with overflow

We have a big C++ project where we rely on compiler warnings and Flexelint to identify potential programming errors. I was curious about how they will warn us once we accidentally try to cast an enum value to a narrower integer.
As suggested by Lint, we usually perform static casts from the enum to the integer. Lint doesn't like implicit casts. We usually cast to the exact type expected by the method.
I got interesting results, see this experiment:
#include <iostream>
#include <string>
#include <stdint.h>
void foo(uint8_t arg)
{
std::cout << static_cast<int>(arg) << std::endl;
}
enum Numbers
{
hundred = 100,
thousand = 1000,
};
int main()
{
std::cout << "start" << std::endl;
foo(static_cast<uint8_t>(hundred)); // 1) no compiler or lint warning
foo(static_cast<uint8_t>(thousand)); // 2) no compiler or lint warning
foo(static_cast<int>(thousand)); // 3) compiler and lint warning
foo(thousand); // 4) compiler and lint warning
std::cout << "end" << std::endl;
}
http://cpp.sh/5hpyz
First case is not a concern, just to mention the good case.
Interestingly, I only got compiler warnings in the latter two cases, when doing an implicit cast. The explicit cast in case 2) will truncate the value (output is 232 like the following two) but with no warning. Ok, the compiler is probably assuming I know what I'm doing here with my explicit cast to uint8_t. Fair enough.
I expected Lint to help me out here. I run this code in Gimpel's online Lint but didn't get any warnings either. Only in the latter two cases again, with this warning:
warning 569: loss of information (call) in implicit conversion from 'int' 1000 (10 bits) to 'uint8_t' (aka 'unsigned char') (8 bits)
Again, the explicit cast to uint8_t in case 2), that truncates my value, doesn't bother Lint at all.
Given a case where all values in an enum fit into the uint8_t. But in some future, we add bigger values (or say: more than 256 values in total), cast them and without noticing that this will truncate them and get unexpected results.
By default, I always cast to the target variable size (case 2) ). Given this experiment, I wonder if this is a wise approach. Shall I cast to the widest type and rely on implicit casts instead (case 3) )?
What's the right approach to get the expected warnings?
You could also write foo(uint8_t{thousand}); instead of a static_cast. With that, you would get a compiler error/warning if thousand is too large for uint8_t. But I don't know what lint thinks about it
This is a problem I also encountered. What I found to work best is to write a function which performs the cast for you and would generate an error in case something is wrong based on type traits.
#include <type_traits>
#include <limits>
template<class TYPE>
TYPE safe_cast(const Numbers& number)
{
using FROM_TYPE = std::underlying_type_t<Numbers>;
// Might have to add some additional code here to fix signed unsigned comparisons.
if((abs(std::numeric_limits<TYPE>::min()) > static_cast<FROM_TYPE>(number)) ||
(std::numeric_limits<TYPE>::max() < static_cast<FROM_TYPE>(number)))
{
// Throw an error or assert.
std::cout << "Error in safe_cast" << std::endl;
}
return static_cast<TYPE>(number);
}
Hope this wil help
p.s. In case you could rewrite this to compile time with a constexpr you could also uses static_assert.

Clang gives me a warning of signedness change, however the code still produces correct output

I was analyzing some warnings in a codebase and got puzzled by this one generated by Clang
Consider the following C++ code:
#include <iostream>
int main(int , char *[])
{
uint32_t val1 = 10;
int32_t val2 = -20;
int32_t result = val1 + val2;
std::cout << "Result is " << result << "\n";
return 0;
}
Clang gives me the following warning when compiling this code with -Wconversion
<source>:9:25: warning: implicit conversion changes signedness:
'unsigned int' to 'int32_t' (aka 'int') [-Wsign-conversion]
int32_t result = val1 + val2;
~~~~~~ ~~~~~^~~~~~
<source>:9:27: warning: implicit conversion changes signedness:
'int32_t' (aka 'int') to 'unsigned int' [-Wsign-conversion]
int32_t result = val1 + val2;
~ ^~~~
2 warnings generated.
GCC also gives me this warning, however I need to provide -Wsign-conversion to trigger it.
The warning says that val2 will be cast to an unsigned int and therefore will loose it's sign. So far so good. However I was expecting that the code above would produce incorrect output, to my surprise it works perfectly fine.
Result is -10
See the program running on both compilers on godbolt.
The cast does not happen in the compiled code and val2 keep its original value. The result of the computation is correct. What is the actual danger that this warning is warning me against? How can I trigger this behaviour? Is the warning bogus?
The second conversion is where things become implementation dependent.
The first (evaluation of expression val1+val2) triggers conversion of val2 to unsigned from signed, which is standard-compliant and documented. The result of the expression is therefore unsigned.
The second (conversion of the resulting unsigned back to signed) is where potential problems ensue. If the unsigned value is not within the defined domain of the target signed type (and in this case, it isn't), implementation behavior ensues, which you cannot assume is portable across the known universe.
What is the actual danger that this warning is warning me against?
The potential danger is that you may have been unaware of the implicit sign conversion, and have made it by accident. If it is intentional and behaves as desired, then there is no danger).
How can I trigger this behaviour?
You already have triggered an implicit sign conversion.
That said, if you want to see some output which may be surprising to you, try this instead:
std::cout << val1 + val2;
Is the warning bogus?
Depends on your definition of bogus.
There definitely is an implicit sign conversion in the program, and therefore if you ask the compiler to warn about implicit sign conversions, then it is entirely correct for the compiler to warn about the implicit sign conversion that is in the program.
There is a reason why this warning option is not enabled by default, nor when enabling "all" warnings using -Wall, nor even when enabling "extra" warnings with -Wextra. These sign conversion warnings warn about a program with well defined behaviour, but which may be surprising to someone who is not paying close attention. A program can be meaningful and correct despite this warning.

Integral promotion and operator+=

I need to eliminate gcc -Wconversion warnings. For example
typedef unsigned short uint16_t;
uint16_t a = 1;
uint16_t b = 2;
b += a;
gives
warning: conversion to 'uint16_t {aka short unsigned int}' from 'int' may alter its value [-Wconversion]
b += a;
~~^~~~
I can eliminate this by
uint16_t a = 1;
uint16_t b = 2;
b = static_cast<uint16_t>(b + a);
Is there any way to keep the operator+= and eliminate the warning? Thank you.
EDIT
I use
gcc test.cpp -Wconversion
my gcc version is
gcc.exe (Rev3, Built by MSYS2 project) 7.2.0
I need to eliminate gcc -Wconversion warnings.
You don't say why but this is actually unlikely.
From the GCC wiki page on this switch:
Why isn't Wconversion enabled by -Wall or at least by -Wextra?
Implicit conversions are very common in C. This tied with the fact that there is no data-flow in front-ends (see next question) results in hard to avoid warnings for perfectly working and valid code. Wconversion is designed for a niche of uses (security audits, porting 32 bit code to 64 bit, etc.) where the programmer is willing to accept and workaround invalid warnings. Therefore, it shouldn't be enabled if it is not explicitly requested.
If you don't want it, just turn it off.
Mangling your code with unnecessary casts, making it harder to read and maintain, is the wrong solution.
If your build engineers are insisting on this flag, ask them why, and ask them to stop.
You could build your own abstraction to overload the += operator, something like
template <typename T>
class myVar {
public:
myVar(T var) : val{var} {}
myVar& operator+=(const myVar& t) {
this->val = static_cast<T>(this->val + t.val);
return *this;
}
T val;
};
int main()
{
typedef unsigned short uint16_t;
myVar<uint16_t> c{3};
myVar<uint16_t> d{4};
c += d;
}
It still uses a static_cast, but you only need to use it once and then reuse it. And you don't need it in your main.
IMHO it just adds overhead, but opinions may vary...

C++ COORD error

I'm currently trying to create a Tetris game and when I call this:
void PrintChar(int x, int y, char ch, Colors color) {
COORD c = { y,x };
FillConsoleOutputCharacterW(GameData::handle, ch, 1, c, NULL);
FillConsoleOutputAttribute(GameData::handle, color, 1, c, NULL);
}
this Warning comes up:
C4838 - conversion from 'int' to 'SHORT' requires a narrowing
conversion.
Could someone please explain what is happening here and a small example would be greatly appreciated.
You should use explicit typecast
COORD c = { static_cast<short>(x), static_cast<short>(y) };
You are using copy-list-initialization, a language feature introduced in C++11, that prevents implicit (potentially) lossy conversions. In a C++11-compliant compiler, this construct should really produce an error (not just a warning)1.
One possible solution is to use a static_cast (with direct-list-initialization as a bonus), if you know that the input will never overflow the range of the destination type:
COORD c{ static_cast<SHORT>( x ), static_cast<SHORT>( y ) };
1 Visual Studio issues a warning C4838, in case there is a potentially lossy narrowing conversion, that cannot be evaluated at compile time. If a narrowing conversion of a constant expression does cause loss of information, error C2397 is issued instead. I don't know whether this is compliant with C++11 and C++14, though.

Locating numerical errors due to Integer division

Is there a g++ warning or other tool that can identify integer division (truncation toward zero)? I have thousands of lines of code with calculations that inevitably will have numerical errors typically due to "float = int/int" that need to be located. I need a reasonable method for finding these.
Try -Wconversion.
From gcc's man page:
Warn for implicit conversions that may
alter a value. This includes
conversions between real and integer,
like "abs (x)" when "x" is "double";
conversions between signed and
unsigned, like "unsigned ui = -1"; and
conversions to smaller types, like
"sqrtf (M_PI)". Do not warn for
explicit casts like "abs ((int) x)"
and "ui = (unsigned) -1", or if the
value is not changed by the conversion
like in "abs (2.0)". Warnings about
conversions between signed and
unsigned integers can be disabled by
using -Wno-sign-conversion.
For C++, also warn for conversions
between "NULL" and non-pointer types;
confusing overload resolution for
user-defined conversions; and
conversions that will never use a type
conversion operator: conversions to
"void", the same type, a base class or
a reference to them. Warnings about
conversions between signed and
unsigned integers are disabled by
default in C++ unless
-Wsign-conversion is explicitly enabled.
For the following sample program (test.cpp), I get the error test.cpp: In function ‘int main()’:
test.cpp:7: warning: conversion to ‘float’ from ‘int’ may alter its value.
#include <iostream>
int main()
{
int a = 2;
int b = 3;
float f = a / b;
std::cout << f;
return 0;
}
I have a hard time calling these numerical errors. You asked for integer calculations, and got the correct numbers for integer calculations. If those numbers aren't acceptable, then ask for floating point calculations:
int x = 3;
int y = 10;
int z = x / y;
// "1." is the same thing as "1.0", you may want to read up on
// "the usual arithmetic conversions." You could add some
// parentheses here, but they aren't needed for this specific
// statement.
double zz = 1. * x / y;
This page contains info about g++ warnings. If you've already tried -Wall then the only thing left could be the warnings in this link. On second look -Wconversion might do the trick.
Note: Completely edited the response.
Remark on -Wconversion of gcc:
Changing the type of the floating point variable from float to double makes the warning vanish:
$ cat 'file.cpp'
#include <iostream>
int main()
{
int a = 2;
int b = 3;
double f = a / b;
std::cout << f;
}
Compiling with $ g++-4.7 -Wconversion 'file.cpp' returns no warnings (as $ clang++ -Weverything 'file.cpp').
Explanation:
The warning when using the type float is not returned because of the totally valid integer arithmetics, but because float cannot store all possible values of int (larger ones cannot be captured by float but by double). So there might be a change of value when assigning RHS to f in the case of float but not in the case of double. To make it clear: The warning is not returned because of int/int but because of the assignment float = int.
For this see following questions: what the difference between the float and integer data type when the size is same in java, Storing ints as floats and Rounding to use for int -> float -> int round trip conversion
However, when using float -Wconversion could still be useful to identify possible lines which are affected but is not comprehensive and is actually not intended for that. For the purpose of -Wconversion see docs/gcc/Warning-Options.html and here gcc.gnu.org/wiki/NewWconversion
Possibly of interest is also following discussion 'Implicit casting Integer calculation to float in C++'
The best way to find such error is to have really good unit tests. All alternatives are not good enough.
Have a look at this clang-tidy detection.
It catches cases like this:
d = 32 * 8 / (2 + i);
d = 8 * floatFunc(1 + 7 / 2);
d = i / (1 << 4);