Почему кастинг от 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]) выявили проблему с интегральными правилами преобразования, согласно которым обе эти спецификации, в общем случае, неопределенное поведение. Проблема состоит в том, что преобразование в тип перечисления имеет неопределенное поведение, если значение, подлежащее преобразованию, не находится в диапазоне перечисления.

Для перечислений с неподписанным фиксированным базовым типом это требование является чрезмерно ограничительным, так как преобразование большого значения в беззнаковый целочисленный тип хорошо определено.