Давайте посмотрим на следующий код:
int i = 10;
char c = reinterpret_cast<char&>(i);
[expr.reinterpret.cast] / 11
Выражение glvalue типа T1 может быть приведено к типу «ссылка на T2», если выражение типа «указатель на T1» может быть явно преобразовано в тип «указатель на T2» с помощью reinterpret_cast. Результат ссылается на тот же объект, что и источник glvalue, но с указанным типом.
Таким образом, значение reinterpret_cast<char&>(i)
l с указанным типом char
относится к объекту int
i
.
Чтобы инициализировать c
, нам нужно значение, поэтому применяется преобразование lvalue в rvalue [conv.lval] /3.4:
значение, содержащееся в объекте, указанном в glvalue, является результатом prvalue.
Результатом преобразования L2R является значение, содержащееся в объекте i
. Пока значение i
находится в диапазоне, представленном char
( [expr] / 4 говорит, что в противном случае это UB), переменная c
должна быть инициализирована, чтобы иметь то же самое значение.
Из реализации POV на платформе с прямым порядком байтов это легко сделать, прочитав байт по адресу объекта i
. Однако на платформе с прямым порядком байтов компилятор должен будет добавить смещение, чтобы получить наименее значимый байт. Или прочитайте весь объект int
в регистр и замаскируйте первый байт, что приемлемо для обоих порядков байтов.
Если вы считаете, что приведенный выше код может быть легко обработан компилятором для создания кода, который ведет себя в соответствии со стандартом C ++ 17, подумайте о приведении указателя на int
, указывающего на i
, в указатель на char
. Такое приведение не изменяет значение указателя, т.е. оно все еще указывает на объект int
i
, что означает, что применение оператора косвенности к такому указателю с последующим преобразованием L2R должно вести себя так, как это было описано выше, т.е. извлекать значение объекта int
, если он представлен типом char
.
В следующем коде
int i = 10;
f(reinterpret_cast<char*>(&i)); // void f(char*)
должен ли компилятор корректировать адрес i
с некоторым смещением, если он не знает, что функция f
будет делать со своим аргументом? А также компилятор не знает, что будет передано функции f
. Код выше и функция f
находятся в разных единицах перевода.
Например, если f
разыменовывает указатель для чтения значения через него, он должен получить значение i
, как описано выше. Но он также может быть вызван с указателем на реальный объект char
, поэтому f
не может настроить данный указатель. Это означает, что вызывающая сторона должна настроить указатель. Но что, если f
передает указатель на memcpy
, чтобы скопировать sizeof(int)
байтов в массив символов этого размера и обратно в другой объект int
, как это разрешено [basic.types] / 3 ? Нелегко представить, как здесь настроить указатели для соответствия требуемому (как [basic.types] / 3 , так и [conv.lval] /3.4) поведению.
Итак, что делают существующие реализации, если существуют существующие реализации действительно , соответствующие стандарту C ++ 17?