11

What is the correct way of initializing a container with predetermined std::byte values?

std::array<std::byte, 2> arr{0x36, 0xd0} for array results in

Enum std::byte has not constant to represent the integer value of X

and compiler errors. Vector and initializer lists are a no-go too.

Is std::vector with std::copy and casts really the intended way of handling this?

Jan Schultke
  • 17,446
  • 6
  • 47
  • 96
mmatous
  • 482
  • 6
  • 16
  • A numeric value n can be converted to a byte value using std::byte{n}, due to C++17 relaxed enum class initialization rules. http://en.cppreference.com/w/cpp/types/byte –  Jul 18 '17 at 16:11
  • OK, I know this, and it is perfectly fine for this example. But for, let's say, 20 values... it's cumbersome. Is there really no better way? – mmatous Jul 18 '17 at 16:14
  • 1
    As a matter of interest, why do you want to use std::byte rather than say, unsigned char? –  Jul 18 '17 at 16:17
  • 6
    API I have no control over. Even if I did, there is semantic correctness. We finally have a nice standardized way of telling "these data are not numbers nor characters. They are just values to be passed/sent/whatever". This is my case exactly. Why would I not want to use it? Except the reason for this question, I mean :) – mmatous Jul 18 '17 at 16:25
  • chars _are_ numbers, and I think you will find std::byte very inconvenient to use and maintain, as your question suggests. –  Jul 18 '17 at 16:25
  • Yes. Of course. Everything is a number in the end. But as I was saying, there is a matter of semantics and getting the point across to other people. – mmatous Jul 18 '17 at 16:27
  • 1
    @NeilButterworth Your comment surprises me. Wouldn't the logical conclusion of your comment be, that `std::byte` is useless? It's either "the way" to represent memory in C++17, or its not. I don't see much value in sometimes using it to represent memory depending on whether or not it is convenient. – Nir Friedman Jul 18 '17 at 16:30
  • 4
    @Nir I don't know enough about C++17, and neither does anyone else, because we have not had enough experience of it yet, to say if std::byte is useful or difficult to use. Lots of C++ features have proved to be A Bad Idea in retrospect. For std::byte, how does one go about interfacing it with C code (as a for instance)? –  Jul 18 '17 at 16:33
  • @NeilButterworth You write a generic function that calls `data` on its input, ensures that the return type is `byte*`, and casts into a `char*`. .`some_c_func(c_data(my_array));`. Pretty much analogous to `c_str`, just a free function instead. Not pretty, but there are much uglier things than that which arise when interfacing with C libraries. – Nir Friedman Jul 18 '17 at 17:28

2 Answers2

15

You will have to write std::byte{0x36}, because there is no implicit conversion from an int to a enum class.

std::array<std::byte, 2> arr = {std::byte{0x36}, std::byte{0xd0}};

If you don't want to write std::byte every single time, write a helper function:

template<typename... Ts>
std::array<std::byte, sizeof...(Ts)> make_bytes(Ts&&... args) noexcept {
    return{std::byte(std::forward<Ts>(args))...};
}

auto arr = make_bytes(0x36, 0xd0);
Rakete1111
  • 47,013
  • 16
  • 123
  • 162
  • 6
    You don't really need perfect forwarding when you know everything is char/unsigned char :) – Barry Jul 19 '17 at 00:06
  • 1
    Was about to post my own answer, since I naively would have expected `std::array arr{{0x36}, {0xd0}};` to also work(Spoiler: it doesn't). I think they really need to iron out the inconsistencies a bit more between initializer lists and aggregate initialization. – Tim Seguine Oct 23 '17 at 09:57
  • 1
    @Rakete1111 It has everything to do with initializer lists and aggregate initialization unless I am missing something, since the moral equivalent for a non aggregate type `std::vector arr{{0x36}, {0xd0}};` seems to work fine. – Tim Seguine Oct 24 '17 at 09:08
  • @TimSeguine Came across your comment, and you are right. No idea what I was thinking at the time :) – Rakete1111 Jul 25 '18 at 13:06
  • Above approach did not works in msvs2019. I see the followed error: - error C2440: 'initializing': cannot convert from 'initializer list' to 'std::byte' – AeroSun Jul 18 '19 at 13:08
  • 1
    @TimSeguine: "*I think they really need to iron out the inconsistencies a bit more between initializer lists and aggregate initialization.*" Where is the inconsistency? `std::byte` is not an aggregate, so it would not use aggregate initialization. `std::byte` is not implicitly convertible from `0x36`, so using `{0x36}` to copy-list-initialize one will not work. That's not inconsistent with anything. – Nicol Bolas Jul 18 '19 at 17:32
  • 3
    @NickBolas except for the fact that C++ educators (even Bjarne) like to refer to initializer lists as "uniform initialization syntax". When the brace can have multiple inconsistent meanings, so that is obviously misleading. Yeah, if I know all the rules for initialization, it is clear what should happen. But to anyone who doesn't the differences between initializer lists and aggregate initialization seem arbitrary and mysterious. It is one of the many things that makes advanced and even intermediate C++ IMO unteachable. I have had many confused coworkers come to me because of this stuff. – Tim Seguine Jul 23 '19 at 09:02
  • 1
    @NickBolas, I just think too often we forget what these rules seem like to non-experts. – Tim Seguine Jul 23 '19 at 09:03
3

With C++20, you can utilize to_array to create an array of unsigned char easily, then bit_cast it to an array of byte:

auto arr = std::bit_cast<std::array<std::byte, 2>>(
    std::to_array<unsigned char>({0x36, 0xd0, /*...*/})
);
Ranoiaetep
  • 5,872
  • 1
  • 14
  • 39