Как использовать новый тип std:: byte в местах, где требуется статический стиль unsigned char?

std::byte - это новый тип в С++ 17, который выполнен как enum class byte : unsigned char. Это делает невозможным его использование без соответствующего преобразования. Итак, я сделал псевдоним для вектора такого типа для представления массива байтов:

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

Однако его нельзя использовать в старом стиле: функции, которые принимают его как параметр, терпят неудачу, потому что этот тип не может быть легко преобразован в старый тип std::vector<unsigned char>, например, использование библиотеки zipper

/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>&'

Я попытался выполнить наивные броски, но это тоже не помогает. Итак, если он предназначен для того, чтобы быть полезным, будет ли он действительно полезным в старых контекстах? Единственный метод, который я вижу, - использовать std::transform для использования нового вектора байтов в этих местах:

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;

Что есть:

  • Гадкий.
  • Делает много бесполезных строк (может быть перезаписано, но все же это нужно написать до:)).
  • Копирует память вместо использования уже созданного фрагмента rawBytes.

Итак, как использовать его в старых местах?

Ответы

Ответ 1

Вы упускаете момент, почему std::byte был изобретен в первую очередь. Причина, по которой он был изобретен, состоит в том, чтобы хранить в памяти необработанный байт, не предполагая, что это символ. Вы можете увидеть это в cppreference.

Подобно char и unsigned char, его можно использовать для доступа к необработанной памяти, занятой другими объектами (представление объектов), но в отличие от этих типов, это не символьный тип и не арифметический тип.

Помните, что C++ является строго типизированным языком в интересах безопасности (поэтому неявные преобразования во многих случаях ограничены). Значение: если бы неявное преобразование из byte в char было возможно, это бы победило цель.

Итак, чтобы ответить на ваш вопрос: чтобы использовать его, вы должны разыграть его, когда захотите сделать ему задание:

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

Все остальное не должно работать, по замыслу! Согласитесь, что преобразование некрасиво, но если вы хотите хранить символы, используйте char. Не используйте байты, если вы не хотите хранить необработанную память, которая не должна интерпретироваться как char по умолчанию.

А также последняя часть вашего вопроса, как правило, неверна: вам не нужно делать копии, потому что вам не нужно копировать весь вектор. Если вам временно нужно прочитать byte как char, просто static_cast его в том месте, где вам нужно использовать его как char. Это ничего не стоит и безопасно для типов.


Что касается вашего вопроса в комментарии о приведении std::vector<char> к std::vector<std::byte>, вы не можете этого сделать. Но вы можете использовать необработанный массив внизу. Итак, следующий тип имеет тип (char*):
std::vector<std::byte> bytes;
// fill it...
char* charBytes = reinterpret_cast<char*>(bytes.data()); 

Это имеет тип char*, который является указателем на первый элемент вашего массива, и может быть разыменован без копирования следующим образом:

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

И размер вы получаете от bytes.size(). Это верно, так как std::vector непрерывен в памяти. Обычно вы не можете сделать это с любым другим контейнером std (deque, list и т.д.).

Несмотря на то, что это действительно, это устраняет часть безопасности из уравнения, имейте это в виду. Если вам нужен char, не используйте byte.

Ответ 2

Если ваш код старого стиля принимает диапазоны или итераторы в качестве аргументов, вы можете продолжать использовать их. В тех немногих случаях, когда вы не можете (например, explicit конструкторы, основанные на диапазоне), вы можете теоретически написать новый класс итератора, который оборачивает итератор в unsigned char и преобразует *it в std::byte&.

Ответ 3

Если вы хотите что-то, что ведет себя как байт так, как вы, вероятно, ожидаете, но имя явно отличается от знака без знака, используйте uint8_t из stdint.h. Почти для всех реализаций это, вероятно, будет

typedef unsigned char uint8_t;

и снова неподписанный символ под капотом - но кого это волнует? Вы просто хотите подчеркнуть "Это не тип персонажа". Вам просто не нужно ожидать, что вы сможете иметь две перегрузки некоторых функций: одну для неподписанного символа и одну для uint8_t. Но если вы сделаете это, то компилятор все равно засунет в него нос...