23

The following code does not compile in C++20

#include <iostream>
#include <cstddef>

int main(){ 
    std::byte b {65};
    std::cout<<"byte: "<<b<<'\n';// Missing overload
}

When std::byte was added in C++17, why was there no corresponding operator<< overloading for printing it? I can maybe understand the choice of not printing containers, but why not std::byte? It tries to act as primitive type and we even have overloads for std::string, the recent std::string_view, and perhaps the most related std::complex, and std::bitset itself can be printed.

There are also std::hex and similar modifiers, so printing 0-255 by default should not be an issue.

Was this just oversight? What about operator>>, std::bitset has it and it is not trivial at all.

EDIT: Found out even std::bitset can be printed.

Quimby
  • 17,735
  • 4
  • 35
  • 55
  • 4
    Perhaps there was disagreement on whether it should format as a character or an integer. Seems to me it's more flexible as a type if that choice isn't made for you, but left up to you. – Patrick Roberts Aug 31 '21 at 07:27
  • 1
    @PatrickRoberts Perhaps, but the point of `std::byte` is to not be `unsigned char`, so I would not expect a character. Maybe there was simply no need. – Quimby Aug 31 '21 at 07:31

2 Answers2

22

From the paper on std::byte (P0298R3): (emphasis mine)

Design Decisions

std::byte is not an integer and not a character

The key motivation here is to make byte a distinct type – to improve program safety by leveraging the type system. This leads to the design that std::byte is not an integer type, nor a character type. It is a distinct type for accessing the bits that ultimately make up object storage.

(emphasis mine)

As such, it is not required to be implicitly convertible/interpreted to be either a char or any integral type whatsoever and hence cannot be printed using std::cout unless explicitly cast to the required type.

Furthermore, see How to use new std::byte type in places where old-style unsigned char is needed?.

Jan Schultke
  • 17,446
  • 6
  • 47
  • 96
Ruks
  • 3,886
  • 1
  • 10
  • 22
  • 2
    Okay, I understand the point of not being an integer or charactec, still `std::cout<(b);` is ugly. Any tool that works with memory is at least capable of hex dump. Therefore printing `std::byte` should not be a rocket science, it's just "bytes" after all, that should be the point. So, wrapping the snippet above into `opetaror<<` and `std::hex`, `std::binary` modifiers is everything that one needs to print bytes in all reasonable formats. – Quimby Aug 31 '21 at 08:17
  • 8
    Not providng the feature is not "avoid making choices for the user", it's just tedious, less user-friendly and goes against the purpose of `std::byte` to improve the code readability and safety. Anyway, this might be the best answer I get, I will wait a bit more and accept it if nothing else shows up. – Quimby Aug 31 '21 at 08:18
  • 2
    [Sorry for the long comments] Furthermore I feel that "It is a distinct type for accessing the bits that ultimately make up object storage." and the ability to print `std::bitset` strongly invalidates the presented arguments. – Quimby Aug 31 '21 at 08:22
  • 5
    @Quimby no one mistakes a `std::bitset` for text, nor do they mistake it for something you can do arithmetic to. *Not* providing operations can improve type safety – Caleth Aug 31 '21 at 08:32
  • @Quimby I believe Caleth's reasoning is correct. If there were more operations provided for `std::byte`, people would start using it in cases where it is inappropriate. – Daniel Langr Aug 31 '21 at 08:43
  • 2
    @Caleth , @DanielLangr I am not arguing against missing arithmetic, just IO. For example how is accepting `110` by `bitset<12> x; cin< – Quimby Aug 31 '21 at 09:10
  • 1
    `std::bitset` has a different use to `std::byte`. "It tries to act as primitive type" no, it tries to be *solely* to "access raw memory occupied by other objects". – Caleth Aug 31 '21 at 09:23
  • 10
    @Quimby You write `std::cout<(b);` but I think the point (of the reationale behind the standard) is that `std::cout< – Pablo H Aug 31 '21 at 17:17
  • 1
    @Quimby as mentioned by Caleth it's accessing raw memory, so from that POV it makes sense not to predict the memory interpretation for the programmer because it might be wrong, thus forcing the programmer to think instead of assume for the person implicitly. Though yes, if compared with other langs e.g. Python where you can `repr()` anything and it's still a string, the choice here is most likely just too low-level, not considering any terminals nor caring about them. In parallel, it seems to me like expecting a resolved void pointer be always printable to console. – Peter Badida Aug 31 '21 at 20:07
  • 2
    C++ chooses to be at odds here with 70 years of computing, in which a byte has been understood as a group of bits (of some size that has mostly settled on 8) and which has an identifiable integer value according to a binary enumeration system. – Kaz Sep 01 '21 at 05:58
  • 1
    @PabloH Okay I get that, still my point is exactly what Kaz is saying. – Quimby Sep 01 '21 at 09:43
  • 1
    @PeterBadida I really like the parallel to `void*`, that's almost convincing, thanks. – Quimby Sep 01 '21 at 09:45
  • "...it is *not required* to be implicitly convertible/interpreted to be either a char or any integral type whatsoever *and hence cannot* be printed..." That's a non sequitur: the lack of a default high-level (typed) interpretation does not imply the inability to output it as low-level, raw, untyped data (especially considering that void pointers can be output just fine). _Bytes can always be output as raw numbers_, and that's not an "interpretation", it's just what stored raw computer data is: _numbers_ without meaning. – Sz. Aug 14 '22 at 10:35
8

std::byte is intended for accessing raw data. To allow me to replace that damn uint8_t sprinkled all over the codebase with something that actually says "this is raw and unparsed", instead of something that could be misunderstood as a C string.

To underline: std::byte doesn't "try to be a primitive", it represents something even less - raw data.

That it's implemented like this is mostly a quirk of C++ and compiler implementations (layout rules for "primitive" types are much simpler than for a struct or a class).

This kind of thing is mostly found in low level code where, honestly, printing shouldn't be used. Isn't possible sometimes.

My use case, for example, is receiving raw bytes over I2C (or RS485) and parsing them into frame which is then put into a struct. Why would I want to serialize raw bytes over actual data? Data I will have access to almost immediately?

To sum up this somewhat ranty answer, providing operator overloads for std::byte to work with iostream goes against the intent of this type.

And expressing intent in code as much as possible is one of important principles in modern programming.

jaskij
  • 222
  • 1
  • 7
  • 2
    "This kind of thing is mostly found in low level code where, honestly, printing shouldn't be used. Isn't possible sometimes." This reads as if low level programmers would be *forced* to print their bytes if they were printable. I mean, if printing shouldn't be used wouldn't you just... not use it? Why does it have to straight up *not exist*? – John Kugelman Aug 31 '21 at 22:47
  • @JohnKugelman you assume everyone knows what they are doing ;) I'd much rather take away the tools here and there. – jaskij Aug 31 '21 at 23:21
  • 3
    Printing out the value of a variable (or the contents of a data buffer) is a fairly common debugging technique for low-level code, is it not? Seems like it would be useful to support that. – Jeremy Friesner Sep 01 '21 at 03:52
  • 1
    "Why would I want to serialize raw bytes over actual data" Haven't you already done that by partitioning the raw stream into `std::byte`s? Why not `std::bitset` instead? That would be more raw and closer to the nature of I2C. Out of curiosity, when you launch a debugger and hover over `std::byte` variable, what do you expect to see? [Or `(gdb ) p byte`] What do you want to see? I certainly would not be happy about "this is raw and unparsed". – Quimby Sep 01 '21 at 09:40
  • @Quimby if you can get `std::bitset` to actually have a good layout so that I can do a `reinterpret_cast` or union punt to an array of those, cool. But that's dependent on implementation details of the standard library. – jaskij Sep 01 '21 at 11:31
  • @JeremyFriesner nah, not really. Even if it's available, it more often than not kills any sort of timings. UART is slow, buffered implementation is heavy and semihosting is just meh. – jaskij Sep 01 '21 at 11:34
  • @JanDorniak Fair enough – Quimby Sep 01 '21 at 11:35
  • @Quimby also, I reimplemented `std::byte`, but for one, two and four byte values. Took me a single afternoon, mostly as an exercise to mess around with concepts and template requirements. Nothing stops you from doing that and adding your own `iostream` overloads ;) – jaskij Sep 01 '21 at 11:40
  • 2
    @JanDorniak Of course, I am comfortable with using `unsigned char` as bytes for now. I was just curious why `std::byte` could not be printed, took me by a surprise. – Quimby Sep 01 '21 at 13:07