1

I am trying to pack multiple indices into a byte (as defined by the GIF format):

std::uint8_t global_color_table_flag;    // bit  [0]
std::uint8_t color_resolution;           // bits [1-3]
std::uint8_t sort_flag;                  // bit  [4]
std::uint8_t global_color_table_size;    // bits [5-7]

I wanted to use

std::uint8_t field = (global_color_table_flag << 7) |
                     (color_resolution << 4) | (sort_flag << 3) |
                     (global_color_table_size);

but the left shift operator << promotes my std::uint8_t to int and the compiler complains (-Werror). Why is that so? Using it on an int does not promote it to a bigger int. It is just UB if you use it incorrectly to my understanding. Why is std::uint8_t any different?

If I use std::byte, the behavior is as expected. However, the code is rather ugly because of the issue mentioned above:

std::byte global_color_table_flag_byte{global_color_table_flag};
global_color_table_flag_byte <<= 7;

std::byte color_resolution_byte{color_resolution};
color_resolution_byte <<= 4;

std::byte sort_flag_byte{sort_flag};
sort_flag_byte <<= 3;

std::byte global_color_table_size_byte{global_color_table_size};

std::byte packed_field = global_color_table_flag_byte |
                         color_resolution_byte | sort_flag_byte |
                         global_color_table_size_byte;

file_stream << std::to_integer<std::uint8_t>(packed_field);

Is there a better way to proceed? As a side question, why can't I just write my std::byte to my binary stream? I thought the whole point of this type was to facilitate manipulation of such streams.

Touloudou
  • 2,079
  • 1
  • 17
  • 28
  • 1
    [Why must a short be converted to an int before arithmetic operations in C and C++?](https://stackoverflow.com/q/24371868/995714), [Usual arithmetic conversions in C : Whats the rationale behind this particular rule](https://stackoverflow.com/q/8937676/995714) – phuclv Aug 19 '20 at 13:09

1 Answers1

0

but the left shift operator << promotes my std::uint8_t to int and the compiler complains (-Werror). Why is that so?

Because you have enabled some compiler option that would warn in such case.

why the bit shift operator works differently on int and std::uint8_t.

Because std::uint8_t is a smaller type than int and therefore it is promoted. This is the same for all types smaller than int and all arithmetic operators, not just bit shift.

Is there a better way to proceed?

You could change the compiler options to not warn about correct expressions.

Or you could make some changes to the expression depending on what the compiler warns about. I cannot be more specific without knowing the warning.

I added all warnings

This is generally not very useful. Using -Wall is fine, but it doesn't enable all warnings for a reason. Many warning options excluded from it are counter-productive, have false-positives, or are simply intended for debugging the meaning of complex programs rather than inform about a problem.

why can't I just write my std::byte to my binary stream?

The proposal which added the type into the language didn't propose additional overloads for character streams. The proposal did not include a rationale for this choice. But I suspect it was to keep the changes minimal. I don't know if this was discussed by the committee.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • Thanks, but I am not asking why the compiler complains (-> I added all warnings + `Werror`), but rather why the bit shift operator works differently on `int` and `std::uint8_t`. – Touloudou Aug 19 '20 at 11:11
  • @Touloudou See edited answer. – eerorika Aug 19 '20 at 11:20
  • That's the thing, a left shift on `uint64_t` returns a `uint64_t`. Similarly, a left shift on `uint32_t` returns a `uint32_t`. Everything smaller than that (`uint8_t` and `uint16_t`) gets promoted to `int`. I don't understand the reasoning here. – Touloudou Aug 19 '20 at 11:26
  • @Touloudou On your system, `uint64_t` and `uint32_t` are not smaller than `int`, and therefore they are not promoted to `int` when used as operand of arithmetic operator. Only smaller integer types are promoted, as I explain in the answer. – eerorika Aug 19 '20 at 11:28
  • I got that part, but I don't understand why the C++ standard decided that "all types smaller than `int` get promoted to `int` by the left shift operator". If I remember correctly, x64 instructions like `shl` don't behave this way. The high-order bit just gets shifted into the carry flag. – Touloudou Aug 19 '20 at 11:42
  • 1
    @Touloudou They simply decided to not change what C does. I don't know if they even considered changing it. – eerorika Aug 19 '20 at 11:46