Почему кастинг от char до std :: byte потенциально неопределенного поведения?
std::byte
из С++ 17 требуется класс перечисления:
enum class byte : unsigned char {};
Мы можем использовать этот std::byte
для представления необработанной памяти вместо одного из char
поскольку она более безопасна для типов, имеет определенные байтовые операторы и не может продвигать int
из синего, как char
. Нам нужно использовать явные приведения или to_integer
для преобразования std::byte
в другие целые числа. Однако из множества источников мы все равно получаем char
(или, скорее всего, целые буферы char
) и поэтому можем его конвертировать:
void fn(char c)
{
std::byte b = static_cast<std::byte>(c);
// ... that may invoke undefined behavior, read below
}
Подпись char
определяется реализацией, поэтому std::numeric_limits<char>::is_signed
может быть true
. Поэтому выше c
может иметь отрицательные значения, которые находятся вне диапазона unsigned char
.
Теперь в стандарте С++ 17 в пункте 8.2.9 Static cast [expr.static.cast] мы можем прочитать, что:
Значение интегрального или перечисляемого типа может быть явно преобразовано в полный тип перечисления. Значение не изменяется, если исходное значение находится в пределах диапазона значений перечисления (10.2). В противном случае поведение не определено.
А из 10.2 видно, что указанный диапазон - это диапазон базового типа. Поэтому, чтобы избежать неопределенного поведения, мы должны написать больше кода. Например, мы можем добавить приведение к unsigned char
для достижения определенных эффектов модульной арифметики во время трансляции:
void fn(char c)
{
std::byte b = static_cast<std::byte>(static_cast<unsigned char>(c));
// ... now we have done it in portable manner?
}
Я что-то неправильно понял? Разве это не слишком сложно и ограничительно? Почему enum class
который имеет неподписанный базовый тип, следует за модульной арифметикой, как это делает его базовый тип? Обратите внимание, что вся строка приводов, скорее всего, не компилируется компилятором. char
, когда он подписан должен быть два дополнением, так как С++ 14 и поэтому его побитовое представление должно быть такими же, как после модульного арифметического преобразования в unsigned char
. Кому выгодно это формальное неопределенное поведение и как?
Ответы
Ответ 1
Это будет исправлено в следующем стандарте:
Значение интегрального или перечисляемого типа может быть явно преобразовано в полный тип перечисления. Если тип перечисления имеет фиксированный базовый тип, значение сначала преобразуется в этот тип путем интегрального преобразования, если необходимо, а затем в тип перечисления. Если тип перечисления не имеет фиксированного базового типа, значение не изменяется, если исходное значение находится в пределах диапазона значений перечисления ([dcl.enum]), и в противном случае поведение не определено
Ниже приведено обоснование изменения от (С++ 11) неуказанного к (С++ 17) undefined: 
Хотя проблема 1094 пояснила, что значение выражения типа перечисления может не находиться в пределах значений значений перечисления после преобразования в тип перечисления (см. 8.2.9 [expr.static.cast], пункт 10), результат это просто неопределенное значение. Вероятно, это должно быть усилено для создания неопределенного поведения в свете того факта, что неопределенное поведение делает выражение непостоянным.
И вот причина для исправления С++ 2a:
Спецификации std :: byte (21.2.5 [support.types.byteops]) и битмаски (20.4.2.1.4 [bitmask.types]) выявили проблему с интегральными правилами преобразования, согласно которым обе эти спецификации, в общем случае, неопределенное поведение. Проблема состоит в том, что преобразование в тип перечисления имеет неопределенное поведение, если значение, подлежащее преобразованию, не находится в диапазоне перечисления.
Для перечислений с неподписанным фиксированным базовым типом это требование является чрезмерно ограничительным, так как преобразование большого значения в беззнаковый целочисленный тип хорошо определено.