7

The following code compiles without warning:

std::uint16_t a = 12;
std::uint16_t b = a & 0x003f;

However, performing a bit shift along with the bitwise and leads to an 'implicit cast warning':

std::uint16_t b = (a & 0x003f) << 10; // Warning generated.

Both gcc and clang complain that there is an implicit conversion from int to uint16_t, yet I fail to see why introducing the bit shift would cause the right hand expression to suddenly evaluate to an int.

EDIT: For clang, I compiled with the -std=c++14 -Weverything flags; for gcc, I compiled with the -std=c++14 -Wall -Wconversion flags.

Alessandro Power
  • 2,395
  • 2
  • 19
  • 39
  • Expression evaluates to an `int` in both cases. Would you provide compiler settings (maybe an online example) for this warning? I don't get this warning in a quick experiment, neither from GCC nor from clang – AnT stands with Russia Apr 29 '16 at 14:25
  • @AnT Try `-Wconversion`. You should see the warning then. – Andrew Apr 29 '16 at 14:29
  • 1
    _"If the operand passed to an arithmetic operator is integral or unscoped enumeration type, then before any other action (but after lvalue-to-rvalue conversion, if applicable), the operand undergoes integral promotion. "_ [http://en.cppreference.com/w/cpp/language/operator_arithmetic](http://en.cppreference.com/w/cpp/language/operator_arithmetic) Also, see [this](http://stackoverflow.com/questions/36925291/bit-shifting-left-and-discarding-bits?noredirect=1#comment61414105_36925291). – zdf Apr 29 '16 at 14:34
  • @ZDF The problem seems deeper than that. `static_cast`ing all quantities to `std::uint16_t` doesn't make the warnings go away. – Alessandro Power Apr 29 '16 at 14:42
  • 1
    @Alessandro Power: Static-casting operands is not supposed to make it go away - they are immediately promoted back to `int` anyway. The only thing that you can static_cast in this case (to make it go away) is the final result. – AnT stands with Russia Apr 29 '16 at 14:51
  • You will get the warning as long as you use types that are narrower than an `int`. I would use `unsigned int` instead of `uint16_t` and cast it once I am done with the calculations. – zdf Apr 29 '16 at 14:56

3 Answers3

5

From cppreference.com: "If the operand passed to an arithmetic operator is integral or unscoped enumeration type, then before any other action (but after lvalue-to-rvalue conversion, if applicable), the operand undergoes integral promotion."

For instance:

byte a = 1;
byte b = a << byte(1);
  1. a and 1 are promoted to int: int(a) and int(byte(1)).
  2. a is shifted one position to left: int result = int(a) << int(byte(1)) (the result is an int).
  3. result is stored in b. Since int is wider than byte an warning will be issued.

If the operands are constant expressions, a compiler might be able to compute the result at compile time and issue an warning iff the result does not fit in destination:

byte b = 1 << 1; // no warning: does not exceed 8 bits
byte b = 1 << 8; // warning: exceeds 8 bits

Or, using constexpr:

constexpr byte a = 1;
byte b = a << 1; // no warning: it fits in 8 bits
byte b = a << 8; // warning: it does not fit in 8 bits
zdf
  • 4,382
  • 3
  • 18
  • 29
4

Doing any arithmetic with integer types always is preceded by promotion to at least (sometimes, but not in this case using gcc, unsigned) int. As you can see by this example, this applies to you first, warning-less variant too.

The probably best way to get around these (admittedly often surprising) integer promotion rules would be to use an unsigned int (or uint32_t on common platforms) from the get go.

If you cannot or don't want to use the bigger type, you can just static_cast the result of the whole expression back to std::uint16_t:

std::uint16_t b = static_cast<std::uint16_t>((a & 0x003f) << 10); 

This will correctly result in the RHS-value mod 2^16.

Baum mit Augen
  • 49,044
  • 25
  • 144
  • 182
1

yet I fail to see why introducing the bit shift would cause the right hand expression to suddenly evaluate to an int.

I think you misinterpret the warning. In both cases expression evaluate to int but in the first case result will always fit in uint16_t, in the second case it will not. Looks like the compiler is smart enough to detect that and generates warning only in the second case.

Slava
  • 43,454
  • 1
  • 47
  • 90