Почему запрещенные ссылки на битовые поля запрещены?

Раздел 9.6/3 в С++ 11 необычайно ясен: "Неконстантная ссылка не должна привязываться к битовому полю". Какова мотивация этого запрета?

Я понимаю, что невозможно напрямую привязать ссылку к битовому полю. Но если я объявлю что-то вроде этого,

struct IPv4Header {
  std::uint32_t version:4,         // assumes the IPv4 Wikipedia entry is correct
                IHL:4,
                DSCP:6,
                ECN:2,
                totalLength:16;
};

Почему я не могу сказать это?

IPv4Header h;

auto& ecn = h.ECN;

Я ожидаю, что базовый код будет фактически привязан ко всему std::uint32_t, который содержит интересующие меня биты, и я ожидаю, что операции чтения и записи будут генерировать код для соответствующей маскировки. Результат может быть большим и медленным, но мне кажется, что он должен работать. Это было бы согласуется с тем, как стандарт говорит, что ссылки на const битовые поля работают (опять же из 9.6/3):

Если инициализатор для ссылки типа const T & является значением l, которое ссылается на бит-поле, ссылка привязана к временному инициализированному удерживать значение битового поля; ссылка не привязана непосредственно к битовому полю.

Это говорит о том, что проблема с записью в битполы - проблема, но я не понимаю, что это такое. Я рассмотрел возможность того, что необходимая маскировка может вводить расы в многопоточном коде, но на 1.7/3 смежные битовые поля ненулевой ширины считаются одним объектом для целей многопоточности. В приведенном выше примере все битовые поля в объекте IPv4Header будут считаться одним объектом, поэтому многопоточный код, пытающийся модифицировать поле при чтении других полей, по определению уже будет искушенным.

Мне явно чего-то не хватает. Что это?

Ответ 1

Неконцевые ссылки не могут быть привязаны к битовым полям по той же причине, указатели не могут указывать на битовые поля.

Пока не указано, занимаются ли ссылки хранением, ясно, что в нетривиальных случаях они реализуются как маскирующие указатели, и эта реализация ссылок "предназначена" авторами языка. И точно так же, как указатели, ссылки должны указывать на адресный блок хранения. Невозможно связать неконстантную ссылку на блок хранения, который не адресуется. Поскольку ссылки, отличные от констант, требуют прямого связывания, ссылка не const может не привязываться к битовому полю.

Единственный способ создания указателя/ссылки, который может указывать на бит-поля, - это реализовать какой-то "суперпоинтер", который в дополнение к фактическому адресу в хранилище также будет содержать какой-то бит-смещение и бит- информацию о ширине, чтобы указать код записи, какие биты изменить. Обратите внимание, что эта дополнительная информация должна присутствовать во всех типах указателей данных, так как такого типа в С++ нет как "указатель/ссылка на бит-поле". Это в основном эквивалентно внедрению модели адресации хранилища более высокого уровня, полностью отделенной от модели адресации, предоставляемой базовой операционной системой/аппаратной платформой. Язык С++ никогда не предполагал, чтобы эта абстракция из базовой платформы не учитывала соображения чистой эффективности.

Одним жизнеспособным подходом было бы введение отдельной категории указателей/ссылок, таких как "указатель/ссылка на бит-поле", которая имела бы более сложную внутреннюю структуру, чем обычный указатель/ссылка на данные. Такие типы могут быть конвертированы из обычных указателей данных/ссылочных типов, но не наоборот. Но, похоже, это не стоит того.

В практических случаях, когда мне приходится иметь дело с данными, упакованными в биты и последовательности бит, я часто предпочитаю реализовывать бит-поля вручную и избегать битовых полей на уровне языка. Имя битового поля - это объект времени компиляции без возможности выбора времени выполнения. Когда требуется выбор времени выполнения, лучшим подходом является объявление обычного поля данных uint32_t и управление отдельными битами и группами бит внутри него вручную. Выбор во время выполнения такого "битового поля" вручную может быть реализован с помощью масок и сдвигов (оба могут быть значениями времени выполнения). В принципе, это близко к ручной реализации вышеупомянутых "суперпоследователей".

Ответ 2

Вы не можете ссылаться на поле бит const по той же причине, по которой вы не можете принять свой адрес с помощью &: его фактический адрес не обязательно совпадает с char, который является наименьшей адресуемой единицей памяти в абстрактной машине С++. Вы можете взять ссылку const на него, потому что компилятор может свободно копировать это значение, поскольку он не будет мутирован.

Рассмотрим вопрос о отдельной компиляции. Функция, использующая const uint32_t&, должна использовать один и тот же код для работы с любым const uint32_t&. Если для обычных значений и значений битовых полей требуется различное поведение записи, то тип не кодирует достаточную информацию для правильной работы функции на обоих.