1

I have encountered the following VS2015 CTP6 VC++ compiler behavior:

unsigned char a = 1;

unsigned char x = (2 | a);   //this compiles fine

unsigned char y[] = {2 | a}; //this emits warning C4838: conversion from
                             //'int' to 'unsigned char' requires a narrowing conversion

I assume that some implicit type conversion or promotion occurs in the definition of y, but whatever is happening there should've happened in the line that defines x as well - yet this line compiles fine.

Reversing the operands order didn't help, casting 2 to unsigned char didn't help either:

unsigned char y[] = {(unsigned char)2 | a}; //same warning
unsigned char y[] = {a | 2};                //same warning

Same thing happens with other bitwise operators as well. The only thing that resolved the warning was an explicit cast of the result of bitwise operation: unsigned char y[] = {(unsigned char)(2 | a)};

Can anyone explain such compiler behavior? Is it a bug?

Edit:

GCC 4.9.1 compiles cleanly, Clang 3.5 issues an error: "error: non-constant-expression cannot be narrowed from type 'int' to 'unsigned char' in initializer list [-Wc++11-narrowing]". Can anyone explain?

SomeWittyUsername
  • 18,025
  • 3
  • 42
  • 85
  • 1
    Before you accuse a compiler of having a bug, it's good to try on several other popular modern compilers. For example, rextester.com would let you easily try that code on both gcc and clang, with no need to install them on your own computer. – Ben Voigt Apr 29 '15 at 22:29
  • How is a warning a bug? A compiler is pretty free to issue them on perfectly good code, or silently accept dubious code. – MSalters Apr 29 '15 at 22:45
  • I think bug's the wrong word, but I'm curious as to the logic. This compiles with no warnings on VS2013. – mock_blatt Apr 29 '15 at 22:49
  • @BenVoigt It was a question, not an accusation. And since the compiler isn't in a release version, I think it's not a long shot to assume it has problems... Anyway, I followed your suggestion and got some interesting results. GCC 4.9.1 compiles cleanly, Clang 3.5 issues an error: "*error: non-constant-expression cannot be narrowed from type 'int' to 'unsigned char' in initializer list [-Wc++11-narrowing]*". Can anyone explain? – SomeWittyUsername Apr 29 '15 at 22:50
  • @MSalters Still there should be a valid explanation for the warning – SomeWittyUsername Apr 29 '15 at 22:51
  • Well, there *is* an explanation right there in the message. "Narrowing conversions" are known risky. – MSalters Apr 29 '15 at 22:55
  • @MSalters Ok, so why the same doesn't apply for initialization of `x`? Moreover, why in `Clang` it's an error, not warning? Error must be backed up by a standard. Also, why this fails as well: `unsigned char y[] = {(unsigned char)2 | a};`? Why we still have a promotion to `int` here? – SomeWittyUsername Apr 29 '15 at 22:59
  • @icepack: That's in the clang error too. "in initialization list". `y` has an initialization list, `x` doesn't. You have a promotion in `(unsigned char)2 | a` because the `|` operator causes the usual promotions. This is not conversion of one operand to the type of the other, this is promotion. – Ben Voigt Apr 29 '15 at 23:12
  • @BenVoigt You mean there is a promotion of the *result* of operation `|` to `int` ? – SomeWittyUsername Apr 29 '15 at 23:25
  • @icepack the operands of `|` are promoted to `int` if they are narrower integral types, which `unsigned char` is. Same with `+`, `-`, `*`, `/`, `%`, `&`, etc. – M.M Apr 29 '15 at 23:29
  • The workaround you are looking for is `unsigned char y[] = { (unsigned char)(2|a) };` which is pretty ugly, so I guess you could look for another way to do what you're trying to do – M.M Apr 29 '15 at 23:30
  • @MattMcNabb Yes, I'm aware of the workaround. What's a bit not clear to me is when the promotion occurs and whether it's mandatory or optional. Is it correct that the promotion occurs when the operands are being evaluated for the operation, i.e. after casting (so the casting has no effect)? Also, I read that the the promotion *may* occur, but not mandatory: http://en.cppreference.com/w/cpp/language/implicit_cast – SomeWittyUsername Apr 29 '15 at 23:35

3 Answers3

4

The rules for aggregate initialization changed between C++03 and today -- a lot. (All quotes from section [dcl.init.aggr] which remains 8.5.1 in all versions)

In C++03, the rule was

All implicit type conversions are considered when initializing the aggregate member with an initializer from an initializer-list.

In draft n3485 (roughly, C++11) and n4296 (roughly, C++14), this case is now covered by a different rule

When an aggregate is initialized by an initializer list, as specified in 8.5.4, the elements of the initializer list are taken as initializers for the members of the aggregate, in increasing subscript or member order. Each member is copy-initialized from the corresponding initializer-clause. If the initializer-clause is an expression and a narrowing conversion is required to convert the expression, the program is ill-formed.

This was an intentional breakage of backward compatibility, and listed as such in the C++11 appendix:

  • Change: Narrowing restrictions in aggregate initializers

  • Rationale: Catches bugs.

  • Effect on original feature: Valid C++ 2003 code may fail to compile in this International Standard. For example, the following code is valid in C++ 2003 but invalid in this International Standard because double to int is a narrowing conversion:

    int x[] = { 2.0 };
    

The semantics of promotion which cause OR-ing unsigned char values to yield a result that doesn't fit back into unsigned char without narrowing have already been fully explained here:

Community
  • 1
  • 1
Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • Thanks for the answer, that explains a lot. What I still miss here is a clear definition of when the promotion occurs and whether it's a must (once it happened, the rest is clear according to the quotes that you added). – SomeWittyUsername Apr 29 '15 at 23:39
  • @icepack: That should be a separate question, IMO. Do you need help looking for an existing one? – Ben Voigt Apr 29 '15 at 23:40
  • @icepack: I think this is the question you're looking for: http://stackoverflow.com/q/28142902/103167 I'll link it from my answer. – Ben Voigt Apr 29 '15 at 23:43
  • Thanks for the link. According to the quote inside, there shouldn't be any arithmetic promotion in case of 2 operands of the same type, like in this case: `unsigned char y[] = {(unsigned char)2 | a};` So the only promotion here is an integral one and the standard is kind of vague here: "*A prvalue of an integer type other than bool, char16_t, char32_t, or wchar_t whose integer conversion rank (4.13) is less than the rank of int can be converted to a prvalue of type int*". The wording is not decisive, just "can"... – SomeWittyUsername Apr 29 '15 at 23:52
  • 1
    @icepack: The wording is a bit weird. First "these conversions CAN be done". Then "this set of conversions form the *integral promotions*", meaning that if said conversion isn't done (which the *can* wording allows), then no integral promotion has been done. Finally, the bold portion in the linked answer, which says "integral promotions SHALL be performed", which rules out exercising the permission not to do them which the word *can* gave. – Ben Voigt Apr 29 '15 at 23:56
  • Great! That probably was the original meaning, but as usual with C++ they've managed to define it as complex as possible :) – SomeWittyUsername Apr 29 '15 at 23:58
2

(2 | a) is of type int, because 2 is an int, and agets promoted.

C++ inherited from C an ability to silently perform unsafe conversions from a wider type to a smaller type - unsafe in the sense that any higher order bits are lost.

That's what this does:

unsigned char x = (2 | a);

The new brace initialisation syntax is an opportunity to ditch this backward compatibility. By design, if you want an unsafe/lossy conversion when using brace initialisation you must ask for it. That's why this doesn't work:

unsigned char y[] = {2 | a}; 

Initialising an unsigned char from int loses information so needs a cast.

This isn't a bug, it's required behaviour. And one day it will catch a bug at compile time that would have taken you hours to find when it happened at run time, so always use the new syntax when you can.

Alan Stokes
  • 18,815
  • 3
  • 45
  • 64
  • 1. This doesn't work either: `unsigned char y[] = {(unsigned char)2 | a};`. 2. I think you're confusing the new brace init syntax with the old array initialization syntax (`y` is an array). – SomeWittyUsername Apr 29 '15 at 22:41
  • 2
    @icepack: The | operator performs integer promotions (like almost all binary operators) so it will always return an integer type at least as wide as an int, even if you write `(char)2 | (char)a`. You need to cast the *result* of the | – rici Apr 29 '15 at 22:52
  • @rici That's not the question. What's not clear is why this conversion seemlesly converts back to a narrower type for a variable initialization but not for an array initialization – SomeWittyUsername Apr 29 '15 at 22:54
  • 1
    @icepack: the array is irrelevant. You should get the same warning with `unsigned char x = { 2 | a };`. As alan says, it' the use of an initialization list. – rici Apr 29 '15 at 23:10
  • 1
    One thing wrong with this answer -- in the array case, brace initialization syntax is not new, and backward compatibility is a concern. – Ben Voigt Apr 29 '15 at 23:13
-1

I'm not sure about Alan's answer because the brace syntax to initialize an array (which is what you're doing there) is valid C syntax, so that's not new to C++.

On VS2013, your code compiles for me with no warnings. I said something else originally, but as I think about it I'm not sure what's proper as far as issuing warnings go.

mock_blatt
  • 955
  • 5
  • 11