Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

c++

c++17

vector

std::byte is a new type in C++17 which is made as enum class byte : unsigned char. This makes impossible to use it without appropriate conversion. So, I have made an alias for the vector of such type to represent a byte array:

using Bytes = std::vector<std::byte>;

However, it is impossible to use it in old-style: the functions which accept it as a parameter fail because this type can not be easily converted to old std::vector<unsigned char> type, for example, a usage of zipper library:

/resourcecache/pakfile.cpp: In member function 'utils::Bytes resourcecache::PakFile::readFile(const string&)':
/resourcecache/pakfile.cpp:48:52: error: no matching function for call to 'zipper::Unzipper::extractEntryToMemory(const string&, utils::Bytes&)'
     unzipper_->extractEntryToMemory(fileName, bytes);
                                                    ^
In file included from /resourcecache/pakfile.hpp:13:0,
                 from /resourcecache/pakfile.cpp:1:
/projects/linux/../../thirdparty/zipper/zipper/unzipper.h:31:10: note: candidate: bool zipper::Unzipper::extractEntryToMemory(const string&, std::vector<unsigned char>&)
     bool extractEntryToMemory(const std::string& name, std::vector<unsigned char>& vec);
          ^~~~~~~~~~~~~~~~~~~~
/projects/linux/../../thirdparty/zipper/zipper/unzipper.h:31:10: note:   no known conversion for argument 2 from 'utils::Bytes {aka std::vector<std::byte>}' to 'std::vector<unsigned char>&'

I have tried to perform naive casts but this does not help also. So, if it is designed to be useful, will it be actually useful in old contexts? The only method I see is to use std::transform for using new vector of bytes in these places:

utils::Bytes bytes;
std::vector<unsigned char> rawBytes;
unzipper_->extractEntryToMemory(fileName, rawBytes);
std::transform(rawBytes.cbegin(),
               rawBytes.cend(),
               std::back_inserter(bytes),
               [](const unsigned char c) {
                   return static_cast<std::byte>(c);
               });
return bytes;

Which is:

  1. Ugly.
  2. Takes a lot of useless lines (can be rewritten but still it needs to be written before:)).
  3. Copies the memory instead of just using already created chunk of rawBytes.

So, how to use it in old places?

like image 547
Victor Polevoy Avatar asked Sep 11 '17 07:09

Victor Polevoy


2 Answers

You're missing the point why std::byte was invented in the first place. The reason it was invented is to hold a raw byte in memory without the assumption that it's a character. You can see that in cppreference.

Like char and unsigned char, it can be used to access raw memory occupied by other objects (object representation), but unlike those types, it is not a character type and is not an arithmetic type.

Remember that C++ is a strongly typed language in the interest of safety (so implicit conversions are restricted in many cases). Meaning: If an implicit conversion from byte to char was possible, it would defeat the purpose.

So, to answer your question: To use it, you have to cast it whenever you want to make an assignment to it:

std::byte x = (std::byte)10;
std::byte y = (std::byte)'a';
std::cout << (int)x << std::endl;
std::cout << (char)y << std::endl;

Anything else shall not work, by design! So that transform is ugly, agreed, but if you want to store chars, then use char. Don't use bytes unless you want to store raw memory that should not be interpreted as char by default.

And also the last part of your question is generally incorrect: You don't have to make copies, because you don't have to copy the whole vector. If you temporarily need to read a byte as a char, simply static_cast it at the place where you need to use it as a char. It costs nothing, and is type-safe.


As to your question in the comment about casting std::vector<char> to std::vector<std::byte>, you can't do that. But you can use the raw array underneath. So, the following has a type (char*):
std::vector<std::byte> bytes;
// fill it...
char* charBytes = reinterpret_cast<char*>(bytes.data()); 

This has type char*, which is a pointer to the first element of your array, and can be dereferenced without copying, as follows:

std::cout << charBytes[5] << std::endl; //6th element of the vector as char

And the size you get from bytes.size(). This is valid, since std::vector is contiguous in memory. You can't generally do this with any other std container (deque, list, etc...).

While this is valid, it removes part of the safety from the equation, keep that in mind. If you need char, don't use byte.

like image 136
The Quantum Physicist Avatar answered Oct 18 '22 13:10

The Quantum Physicist


If you want something that behaves like a byte in the way you'd probably expect it but is named distinctly different from unsigned char use uint8_t from stdint.h. For almost all implementations this will probably be a

typedef unsigned char uint8_t;

and again an unsigned char under the hood - but who cares? You just want to emphasize "This is not a character type". You just don't have to expect to be able to have two overloads of some functions, one for unsigned char and one for uint8_t. But if you do the compiler will push your nose onto it anyway...

like image 1
Don Pedro Avatar answered Oct 18 '22 14:10

Don Pedro