r/cpp • u/comfortcube • 4d ago
Might have found a (tiny, nuisance) bug in g++ 16.2.1 and -Wconversion?
I am far from presuming myself a compiler expert, and no this was not AI, I promise. I'm simply a long-time user of gcc/g++, and it's not everyday I am confident enough that a bug is not in my code but with the tool, haha. I'm pretty warning-sensitive so this kind of thing catches my attention. Wondering if I'm missing something, so asking it here first.
g++ version: g++ (GCC) 16.2.1 20260819 (Red Hat 16.2.1-2) (I'm on Fedora 44)
C++20
Given lines like this:
std::uint16_t count = 0;
// int other_var set elsewhere
count += (other_var == 5); // <-- -Wconversion flags this
specifically, the warning is:
<file>:<line>:<col>: warning: conversion from ‘int’ to ‘uint16_t’ {aka ‘short unsigned int’} may change value [-Wconversion]
<line> | count += (other_var == 5);
-Wconversion /w either -O0 and -O3 (my usual build optimization lvls to catch optimization-dependent warnings), this gets flagged.
Well, the result of a boolean expression is 0 or 1, so obviously, there should not be a conversion. Even with the accumulate op here and integer promotion, there shouldn't be a warning (you can do count += 1 or count++ and it definitely doesn't flag -Wconversion). And more notably, the below code doesn't get flagged:
std::uint16_t count = 0;
// int other_var set elsewhere
bool match = (other_var == 5);
count += match;
I also noted that clang with -Wconversion and the same optimization lvls does not flag this. I also know I've done this before in C with other versions of gcc and don't get flagged with this.
So, am I missing something or am I right to suspect this is a possible bug with at least my version of g++?
19
u/ezoe 4d ago
GCC manual said
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).
https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html#index-Wconversion
Meaning, it doesn't warn ALL implicit conversions. It warns ONLY IF it "may alter a value"
Given that, bool to std::uint16_t conversion never alter a value. The values of bool is either 0 or 1. Both can be expressed without a possibility of altering a value by std::uint16_t.
5
u/TheRealSmolt 4d ago
Yeah but the problem is that it's not
booltostd::uint16_t, it'sbooltointtostd::uint16_t. So it's seeing the possibility of narrowing a value.14
2
u/comfortcube 4d ago
Yeah, but it's not doing so consistently, if that was the case. If I use
bool match = (a == 5);thencount += match, that's fine.8
u/TheRealSmolt 4d ago
The implicit conversion I'm deriving from applies only to prvalues, so no idea about the lvalue version.
8
u/comfortcube 3d ago
Ah, that's a good point you mentioned it. I didn't pay attention to the lvalue vs prvalue nuance.
13
1
u/comfortcube 4d ago
Given that, bool to std::uint16_t conversion never alter a value. The values of bool is either 0 or 1. Both can be expressed without a possibility of altering a value by
std::uint16_t.Yeah exactly, that's why I wonder if this is a tiny bug or not.
booltostd::uint16_tshould be fine ¯_(ツ)_/¯2
u/ezoe 4d ago
The important point is, GCC is not a standard. GCC is an implementation of standard.
GCC don't see it as arbitrary value of int to std::uint16_t conversion. GCC can determine value can be either 0 or 1 because it originated from bool and nothing else from the context.
This GCC option don't warn implicit TYPE conversion. GCC warn implicit VALUE conversion.
6
u/StaticCoder 3d ago
The importanter point is that this is a warning. The standard doesn't mandate any warnings.
1
u/bert8128 3d ago
OP didn’t mention the c++ standard. They said that the warning is triggered for a case where there can be no conversion error.
2
1
u/jwakely libstdc++ tamer, LWG chair 2d ago
It's not bool to uint16. You're adding a uint16 and bool, which covers to int + int, which can be larger than UINT16_MAX, so when converted back to uint16 it doesn't fit.
If the uint16 value is originally zero then that can't happen, but at -O0 the compiler doesn't do value propagation, so it only looks at the types involved not the values.
This definitely changes the value from an int with value
UINT16_MAX+1to a uint16_t with value 0:uint16_t count = UINT16_MAX; count += true;It's equivalent to:
count = (int)count + (int)true;So it's not true that adding bool to uint16_t can't give the wrong result.
But I'm not sure why GCC warns inconsistently depending on the type involved on the right hand side of the +=, I haven't looked into the details.
6
u/sheckey 3d ago
I know it’s not your point, and your point is revealing notable conversion issues (that are new to me, so thank you) but I have never really liked these kind of logic-as-computation statements, even back in the K&R days. I always have to mentally decode them. I prefer “if (match) ++count”. You have reminded me again to watch out for this kind of thing in our ancient code base. Thanks!
5
u/comfortcube 3d ago edited 3d ago
Ah, I definitely get you.
I tend to reach for these kind of statements when I want to explicitly force avoiding branching for performant code (which is where I noticed this), cuz branch prediction penalties can drop performance.
That said, I am almost certain that many compilers know to optimize this kind of thing even if one uses the
ifstatement, and often whenever I compare assembly output with both forms, they collapse to the same. It's just a habit of mine at this point and a slight lack of confidence in compiler optimization capabilities. I should probably use the branched version more often.Edit: Did a quick test on Godbolt /w gcc 16.2 and yup, branch vs unbranched yields the same assembly: https://godbolt.org/z/7Mn6o3Go9
3
5
u/azswcowboy 3d ago
Yeah modern compilers are really good at optimizing these simpler cases now. The other week I noticed that pass by value std::string was same assembly as std::string_view by value. The compiler noticed that the inline function didn’t modify the passed string and elided the copy. These days I tend to write the code I want to read and only worry about optimizing if it’s in a critical hot path.
24
u/TheRealSmolt 4d ago
I think the standard defined implicit
boolconversion is tointspecifically. That's how I read cppreference, at least. I wouldn't call this a bug, though it is an interesting regression if what you are saying about the versions is true.