Consider the following code:
uint32_t foo(uint64_t x ) {
auto y = uint32_t { x };
return y;
}
It is considered a narrowing conversion the compiler feels compelled to warn me about (GCC 9) or even declare an error (clang 9): GodBolt.
My questions:
Why is uint32_t { x } less explicit than static_cast<uint32_t>(x)?
Why is this more severe with clang than with GCC, meriting an error?
Why is uint32_t { x } less explicit than static_cast<uint32_t>(x)?
It's not less explicit, it's just not allowed. Narrowing conversions are not allowed when doing direct or copy list initialization. When you do auto y = uint32_t { x }; you are direct-list-initializing y with a narrowing conversion. (Guaranteed Copy elision means there is no temporary here anymore)
Why is this more severe with clang than with GCC, meriting an error?
It's up the the implementers. Apparently clang wants to be more strict and issue a hard error, but both are fine. The standard only requires a diagnostic message be given, and a warning or error covers that.
Adding to #NathanOliver's answer - the warnings and errors go away if we construct the 32-bit integer like so:
uint32_t foo(uint64_t x ) {
auto y = uint32_t(x);
return y;
}
So, (x) and {x} here are not semantically equivalent (even if the same constructor would end up getting called, had it been a class). The no-narrowing guarantee in the standard apparently only applies to list-initialization, IIANM.
So, take this is a motivation for using curly-bracket initialization, if you want to be extra careful (or parentheses if you want to not be bothered.)
From https://en.cppreference.com/w/cpp/language/list_initialization:
Narrowing conversions
list-initialization limits the allowed implicit conversions by
prohibiting the following:
...
-conversion from integer or unscoped enumeration type to integer type that cannot represent all values of the original, except where source
is a constant expression whose value can be stored exactly in the
target type
This sounds like clang is more conformant than gcc here (though beware that I'm not a language lawyer)*: the standard mandates that, if you use initalizer lists, you aren't in any danger of a narrowing conversion. This is a conscious design choice to remedy the rather promiscuous implicit conversion built into the language - and the admittedly clear way that you spell it out in your example is a collateral annoyance.
Edit: * and it didn't take long - it seems "not allowed" at cppreference translates to "implementer dependent" in the standard, as per NathanOliver's answer. That's what I get for not checking the source.
Related
I am just researching a three-way comparison operator <=>. I see that it returns std::strong_ordering. However, I fail to understand how the compiler is restricting only 0 in comparison operators (so<0, but not so<1)
#include<compare>
int main()
{
std::strong_ordering so = 55 <=> 10;
so < 0; // Fine
so < 1; // Fails
}
Similarly, so>20 won't work either. Following also won't work:
constexpr int Zero = 0;
so == Zero; // Error
so == 0; // Fine
EDIT - Interesting observation (on MSVC compiler). Following is valid:
so < nullptr
Using anything but a literal 0 to compare against std::strong_ordering is explicit undefined behavior, see [cmp.categories.pre]/3 of the C++20 draft.
It is up to the compiler/standard library how or whether this is enforced/diagnosed.
One way of achieving a diagnostic for the UB without any compiler magic is to use std::nullptr_t as argument to the overloaded comparison operator of std::strong_ordering (which has unspecified type according to the standard). Any integral zero literal can be implicitly converted to std::nullptr_t, but literals with other values or constant expressions that are not literals, cannot. See [conv.ptr]/1.
This is also mentioned in a draft note as possibility.
Libc++ seems to instead use a member pointer to some hidden class, see here.
Libstdc++ seems to do something similar, using a hidden class type that needs to be constructed from a pointer to itself, see here.
However neither of these implementations diagnose all arguments that result in UB according to the standard. In particular all of them also accept nullptr as argument without diagnostic: https://godbolt.org/z/esnvqR
I suppose full diagnosis of all the cases would require some compiler magic.
With code:
#include <cstdint>
uint8_t a() { return 5; }
auto b() {
uint8_t c = 6;
c |= a(); // Warning here
return c;
}
auto d() {
uint8_t c = 6;
uint8_t d = a();
c |= d;
return c;
}
g++ warns (using -Wconversion):
<source>:7:12: warning: conversion from 'int' to 'uint8_t' {aka 'unsigned char'} may change value [-Wconversion]
I assume the problem is related to integer promotion for bit operations, but in the second function d() I assign it to a variable first and then there is no warning.
(clang does not warn about this, only g++)
Can this be solved by casting instead of variable assignment?
Why does it behave differently when I use a function?
Compiler explorer with the above: https://godbolt.org/z/q9eMVT
I think this is a bug in GCC. Based on [expr.ass]/7, the expression
x |= y
is equivalent to
x = x | y
except that a is only evaluated once. In a bitwise inclusive OR, like other bitwise operations, as also noted in CoryKramer's comment above, the usual arithmetic conversions will first be performed on both operands [expr.or]/1. Since both our operands are of type std::uint8_t, the usual arithmetic conversions are simply the integral promotions [expr.arith.conv]/1.5. Integral promotion on std::uint8_t should mean that both operands are converted to int [conv.prom]/1. No further conversions of the operands should be required, since the converted types of both operands are the same. Finally, the int resulting from the | expression is then converted back to std::uint8_t and stored in the object referred to by x [expr.ass]/3. I would assume that this last step is what triggers the warning in some cases. However, there is no way that the result of a bitwise logical OR between two std::uint8_t could possibly not be representable in an std::uint8_t, no matter whether we convert to int (which is guaranteed to be larger) and back along the way. Thus, the warning is unnecessary here, which is probably why it is normally not produced.
The only difference I see between your first and your second version is that a() is an rvalue while d is an lvalue. The value category should have no influence on the behavior of the usual arithmetic conversions, however. Thus, the warning—unnecessary or not—should at least be produced consistently. As you have noted yourself, other compilers, such as clang, will not produce a warning here. Furthermore, the issue seems to be oddly-specific to the involvement of function calls in compound assignment. As noted by SergeyA in another comment above, GCC will not produce the warning in the equivalent form of c = c | a(). Using other kinds of rvalues in place of the function call, such as, for example, the result of casting a literal
c |= static_cast<std::uint8_t>(42);
will also not produce a warning in GCC. But as soon as there is a function call on the right-hand side of the expression, even if the result of the function call is not used at all, e.g., in
c |= (a(), static_cast<std::uint8_t>(5));
the warning appears. Thus, I would conclude that this is a bug in GCC and it would be great if you would write a bug report. …
Is it safe to assume that static_cast will never throw an exception?
For an int to Enum cast, an exception is not thrown even if it is invalid. Can I rely on this behavior? This following code works.
enum animal {
CAT = 1,
DOG = 2
};
int y = 10;
animal x = static_cast<animal>(y);
For this particular type of cast (integral to enumeration type), an exception might be thrown.
C++ standard 5.2.9 Static cast [expr.static.cast] paragraph 7
A value of integral or enumeration type can be explicitly converted to
an enumeration type. The value is unchanged if the original value is
within the range of the enumeration values (7.2). Otherwise, the
resulting enumeration value is unspecified / undefined (since C++17).
Note that since C++17 such conversion might in fact result in undefined behavior, which may include throwing an exception.
In other words, your particular usage of static_cast to get an enumerated value from an integer is fine until C++17 and always fine, if you make sure that the integer actually represents a valid enumerated value via some kind of input validation procedure.
Sometimes the input validation procedure completely eliminates the need for a static_cast, like so:
animal GetAnimal(int y)
{
switch(y)
{
case 1:
return CAT;
case 2:
return DOG;
default:
// Do something about the invalid parameter, like throw an exception,
// write to a log file, or assert() it.
}
}
Do consider using something like the above structure, for it requires no casts and gives you the opportunity to handle boundary cases correctly.
Is it safe to assume that static_cast will never throw an exception?
No. For user-defined types, the constructor and/or conversion operator might throw an exception, resulting in well-defined behavior.
Consider the output of this program:
#include <iostream>
struct A {
A(int) { throw 1; }
};
int main () {
int y = 7;
try {
static_cast<A>(y);
} catch(...) {
std::cout << "caught\n";
}
}
static_cast can't throw exception since static_cast is not runtime cast, if some cannot be casted, code will not compiles. But if it compiles and cast is bad - result is undefined.
(This answer focuses exclusively on the int to enum conversion in your question.)
For an int to Enum cast, an exception is not thrown even if it is invalid. Can I rely on this behavior? This following code works.
enum animal { CAT = 1, DOG = 2 };
int y = 10;
animal x = static_cast<animal>(y);
Actually, enums are not restricted to the list of enumerations in their definition, and that's not just some strange quirk, but a deliberately utilised feature of enums - consider how enumeration values are often ORed together to pack them into a single value, or a 0 is passed when none of the enumerations apply.
In C++03, it's not under explicit programmer control how big a backing integer will be used by the compiler, but the range is guaranteed to span 0 and the explicitly listed enumerations.
So, it's not necessarily true that 10 is not a valid, storable value for an animal. Even if the backing value were not big enough to store the integral value you're trying to convert to animal, a narrowing conversion may be applied - typically this will use however many of the least significant bits that the enum backing type can hold, discarding any additional high order bits, but for details check the Standard.
In practice, most modern C++03 compilers on PC and server hardware default to using a (32 bit) int to back the enumeration, as that facilitates calling into C library functions where 32 bits is the norm.
I would never expect a compiler to throw an exception when any value is shoehorned into an enum using static_cast<>.
In the following program, the line 5 does give overflow warning as expected, but surprisingly the line 4 doesn't give any warning in GCC: http://www.ideone.com/U0BXn
int main()
{
int i = 256;
char c1 = i; //line 4
char c2 = 256; //line 5
return 0;
}
I was thinking both lines should give overflow warning. Or is there something I'm missing?
The topic which led me to do this experiment is this: typedef type checking?
There I said the following(which I deleted from my answer, because when I run it, it didn't show up as I had expected):
//However, you'll get warning for this case:
typedef int T1;
typedef char T2;
T1 x = 256;
T2 y = x; //possible overflow warning! (but it doesn't give warning :()
-Wall doesn't include many options. -Wconversion is one of them and warns about the behavior you're interested in.
See http://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html
In the general case of assigning an int value to a char object, the compiler doesn't know whether the int is out of range of the char.
Look at the actual warning more closely:
warning: overflow in implicit constant conversion
It is in this specific case, where a constant is being converted to char that the compiler is able to warn you. Likewise, if you changed the declaration of i to be const:
const int i = 256;
you will also get the warning, because the value being assigned to c2 is a constant expression.
Note also that the warning is somewhat misleading as the conversion does not technically "overflow." Arithmetic overflow yields undefined behavior in C++. A narrowing conversion (like int to char, if int has a larger range than char) yields some implementation-defined conversion.
Well, line 5 is an obvious error that any compiler can see directly, and always an error. Line 4 would require at least some data flow analysis to discover the error. Perhaps this isn't done with the settings used at the site, or perhaps the compiler writers didn't consider this important enough to figure it out.
Post GCC 4.3, the semantics of -Wconversion have been updated to detect implicit conversions that might change a value, but you have to enable -Wsign-conversion as well, because otherwise you won't get a warning for code that might change the sign of a number due to coercion between signed and unsigned types.
Contrary to what Crazy Eddie is saying, prior to GCC 4.3 (which hadn't been released yet at the time) -Wconversion didn't generically check for problems introduced by implicit type conversion and the like. Rather, it checked whether your program would behave differently than it would have behaved if it had used old-style K&R function prototypes.
This not only meant that it didn't give a warning on all implicit type conversion / coercion problems, but it also meant that some good code gave an unnecessary warning. And of course, you'd get no error with g++ because such prototypes aren't valid C++ anyway.
C++0x is going to make the following code and similar code ill-formed, because it requires a so-called narrowing conversion of a double to a int.
int a[] = { 1.0 };
I'm wondering whether this kind of initialization is used much in real world code. How many code will be broken by this change? Is it much effort to fix this in your code, if your code is affected at all?
For reference, see 8.5.4/6 of n3225
A narrowing conversion is an implicit conversion
from a floating-point type to an integer type, or
from long double to double or float, or from double to float, except where the source is a constant expression and the actual value after conversion is within the range of values that can be represented (even if it cannot be represented exactly), or
from an integer type or unscoped enumeration type to a floating-point type, except where the source is a constant expression and the actual value after conversion will fit into the target type and will produce the original value when converted back to the original type, or
from an integer type or unscoped enumeration type to an integer type that cannot represent all the values of the original type, except where the source is a constant expression and the actual value after conversion will fit into the target type and will produce the original value when converted back to the original type.
I ran into this breaking change when I used GCC. The compiler printed an error for code like this:
void foo(const unsigned long long &i)
{
unsigned int a[2] = {i & 0xFFFFFFFF, i >> 32};
}
In function void foo(const long long unsigned int&):
error: narrowing conversion of (((long long unsigned int)i) & 4294967295ull) from long long unsigned int to unsigned int inside { }
error: narrowing conversion of (((long long unsigned int)i) >> 32) from long long unsigned int to unsigned int inside { }
Fortunately, the error messages were straightforward and the fix was simple:
void foo(const unsigned long long &i)
{
unsigned int a[2] = {static_cast<unsigned int>(i & 0xFFFFFFFF),
static_cast<unsigned int>(i >> 32)};
}
The code was in an external library, with only two occurrences in one file. I don't think the breaking change will affect much code. Novices might get confused, though.
I would be surprised and disappointed in myself to learn that any of the C++ code I wrote in the last 12 years had this sort of problem. But most compilers would have spewed warnings about any compile-time "narrowings" all along, unless I'm missing something.
Are these also narrowing conversions?
unsigned short b[] = { -1, INT_MAX };
If so, I think they might come up a bit more often than your floating-type to integral-type example.
A practical instance that I have encountered:
float x = 4.2; // an input argument
float a[2] = {x-0.5, x+0.5};
The numeric literal is implicitly double which causes promotion.
Try adding -Wno-narrowing to your CFLAGS, for example :
CFLAGS += -std=c++0x -Wno-narrowing
I wouldn't be all that surprised if somebody gets caught out by something like:
float ra[] = {0, CHAR_MAX, SHORT_MAX, INT_MAX, LONG_MAX};
(on my implementation, the last two don't produce the same result when converted back to int/long, hence are narrowing)
I don't remember ever writing this, though. It's only useful if an approximation to the limits is useful for something.
This seems at least vaguely plausible too:
void some_function(int val1, int val2) {
float asfloat[] = {val1, val2}; // not in C++0x
double asdouble[] = {val1, val2}; // not in C++0x
int asint[] = {val1, val2}; // OK
// now do something with the arrays
}
but it isn't entirely convincing, because if I know I have exactly two values, why put them in arrays rather than just float floatval1 = val1, floatval1 = val2;? What's the motivation, though, why that should compile (and work, provided the loss of precision is within acceptable accuracy for the program), while float asfloat[] = {val1, val2}; shouldn't? Either way I'm initializing two floats from two ints, it's just that in one case the two floats happen to be members of an aggregate.
That seems particularly harsh in cases where a non-constant expression results in a narrowing conversion even though (on a particular implementation), all values of the source type are representable in the destination type and convertible back to their original values:
char i = something();
static_assert(CHAR_BIT == 8);
double ra[] = {i}; // how is this worse than using a constant value?
Assuming there's no bug, presumably the fix is always to make the conversion explicit. Unless you're doing something odd with macros, I think an array initializer only appears close to the type of the array, or at least to something representing the type, which could be dependent on a template parameter. So a cast should be easy, if verbose.
Narrowing conversion errors interact badly with implicit integer promotion rules.
I had an error with code which looked like
struct char_t {
char a;
}
void function(char c, char d) {
char_t a = { c+d };
}
Which produces an narrowing conversion error (which is correct according to the standard). The reason is that c and d implicitly get promoted to int and the resulting int isn't allowed to be narrowed back to char in an initializer list.
OTOH
void function(char c, char d) {
char a = c+d;
}
is of course still fine (otherwise all hell would break loose). But surprisingly, even
template<char c, char d>
void function() {
char_t a = { c+d };
}
is ok and compiles without a warning if the sum of c and d is less than CHAR_MAX. I still think this is a defect in C++11, but the people there think otherwise - possibly because it isn't easy to fix without get rid of either implicit integer conversion (which is a relict from the past, when people wrote code like char a=b*c/d and expected it to work even if (b*c) > CHAR_MAX) or narrowing conversion errors (which are possibly a good thing).
It was indeed a breaking change as real life experience with this feature has shown gcc had turned narrowing into a warning from an error for many cases due to real life pains with porting C++03 code bases to C++11. See this comment in a gcc bug report:
The standard only requires that "a conforming implementation shall issue at least one diagnostic message" so compiling the program with a warning is allowed. As Andrew said, -Werror=narrowing allows you to make it an error if you want.
G++ 4.6 gave an error but it was changed to a warning intentionally for 4.7 because many people (myself included) found that narrowing conversions where one of the most commonly encountered problems when trying to compile large C++03 codebases as C++11. Previously well-formed code such as char c[] = { i, 0 }; (where i will only ever be within the range of char) caused errors and had to be changed to char c[] = { (char)i, 0 }
It looks like GCC-4.7 no longer gives errors for narrowing conversions, but warnings instead.