Почему reinterpret_cast не принудительно выполняет copy_n для приведения типов одинакового размера?

Согласно cppreference.com, reinterpret_cast:

Преобразует типы, переинтерпретируя базовый битовый шаблон.

Но подождите, это ложь, потому что это работает только в этих случаях:

When a pointer or reference to object of type T1 is reinterpret_cast (or C-style cast) to a pointer or reference to object of a different type T2, the cast always succeeds, but the resulting pointer or reference may only be accessed if both T1 and T2 are standard-layout types and one of the following is true:

  • T2 — динамический тип объекта (возможно, cv-qualified).
  • T2 и T1 оба (возможно, многоуровневые, возможно, cv-квалифицированные на каждом уровне) указатели на один и тот же тип T3
  • T2 — это подписанный или неподписанный вариант динамического типа объекта (возможно, с указанием cv).
  • T2 — это агрегатный тип или тип объединения, который содержит один из вышеупомянутых типов в качестве элемента или нестатического члена (включая, рекурсивно, элементы подагрегатов и нестатические элементы данных содержащихся объединений): это делает безопасное приведение от первого члена структуры и от элемента объединения до содержащей его структуры/объединения.
  • T2 - это (возможно, cv-квалифицированный) базовый класс динамического типа объекта
  • T2 is char or unsigned char

Согласно этому списку незаконным примером будет:

auto foo = 13LL;
auto bar = reinterpret_cast<double&>(foo);

Таким образом, единственный приемлемый способ сделать это приведение — скопировать память:

auto foo = 13LL;
double bar;

copy_n(reinterpret_cast<char*>(&foo), sizeof(foo), reinterpret_cast<char*>(&bar));

Мой вопрос: почему reinterpret_cast не справится с этим за меня? Или есть что-то еще, чтобы мне не пришлось прыгать через этот обруч?


person Jonathan Mee    schedule 24.02.2015    source источник
comment
Я считаю, что первый вопрос должен быть: почему вы хотите сделать это reinterpret_cast?   -  person Marc Glisse    schedule 24.02.2015
comment
@MarcGlisse Lol, сначала я понял, что сделал здесь что-то не так: stackoverflow.com/a/28634468/2642059 Но после моей ошибки я действительно просто хотел понять, как правильно справиться с этой ситуацией.   -  person Jonathan Mee    schedule 24.02.2015
comment
Почему бы просто не использовать memcpy вместо reinterpret_cast<char*> и copy_n? Если побайтовая копия уместна, используйте memcpy, если это не так, то ваш copy_n тоже не работает.   -  person Jonathan Wakely    schedule 26.03.2015
comment
@JonathanWakely Расскажите, пожалуйста, подробнее, почему я предпочитаю memcpy copy_n, разве это не одно и то же?   -  person Jonathan Mee    schedule 26.03.2015
comment
Поскольку memcpy принимает void*, вам не нужен уродливый reinterpret_cast, потому что, если foo равно const, вам не нужно помнить о приведении к const char*, так как &foo просто делает правильную вещь, потому что memcpy очень сильно оптимизирован, и вы не полагаетесь о реализации, обрабатывающей copy_n из char объектов одинаково, потому что это легче читать и обычным способом сказать скопировать эти байты отсюда сюда и т. д. и т. д. Я смотрю на это copy_n и думаю, что это делает?   -  person Jonathan Wakely    schedule 26.03.2015
comment
@JonathanMee: Если у меня есть указатель, который, как я знаю, идентифицирует объект типа Foo, но мне нужен код для обработки базового хранилища, как если бы это был тип Bar, было бы разумнее сказать Рассматривать этот код как потенциальный косвенный доступ на Foo или Рассматривайте этот код как потенциальный непрямой доступ ко всем объектам всех типов. Обработка компилятором reinterpret_cast указателя как первого избавит программиста от необходимости кодировать второй.   -  person supercat    schedule 30.06.2016


Ответы (2)


почему reinterpret_cast не справится с этим за меня?

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

Делая его неопределенным, компилятор может предположить, что выражения несвязанных типов не обращаются к одному и тому же объекту, что может обеспечить лучшую оптимизацию. Например, в следующем:

int   & i = something();
float & f = something_else();

const int i1 = i;
f = 42;
const int i2 = i;

компилятор может предположить, что i1 и i2 имеют одинаковое значение (i не изменяется при присвоении f), и оптимизировать их в одну константу. Нарушение предположения приведет к неопределенному поведению.

Или есть что-то еще, чтобы мне не пришлось прыгать через этот обруч?

Копирование байтов — единственный четко определенный способ переинтерпретировать один тип объекта как несвязанный тип.

Псевдоним с reinterpret_cast или объединением может иногда работать (при условии, что размер и т. д. совпадают), но может сбить вас с толку, если оптимизатор станет слишком умным с поведением undefined.

person Mike Seymour    schedule 24.02.2015
comment
Этот ответ относится только к reinterpret_cast ссылкам или указателям, а не к числовым значениям. Учитывая, что копирование кода в вопросе рассматривается как решение, и учитывая, что все, что он делает, это переинтерпретирует биты значения, я думаю, что этот ответ вообще не касается конкретной проблемы. Но это нормально как ответ на отдельную проблему ограничений переинтерпретации указателя/ссылки (которую мой ответ не затрагивал). - person Cheers and hth. - Alf; 24.02.2015
comment
@Cheersandhth.-Alf: Я не понимаю, что ты имеешь в виду. Вопрос спрашивает, почему приведения указателя/ссылки разрешены только тогда, когда они указывают/ссылаются на связанные типы; на что я ответил. Числовые преобразования (которые для reinterpret_cast означают преобразование между числовым значением и указателем) не имеют ничего общего с вопросом. - person Mike Seymour; 24.02.2015
comment
Примеры OP интерпретируют значение long long как значение double. Затем он спрашивает, почему reinterpret_cast не справится с этим за меня. Этот ответ не решает эту проблему. Это не очевидно. Один из людей, принимавших участие в создании C++, однажды непреднамеренно написал reinterpret_cast числовых значений в сообщении Usenet. (Только несколько человек когда-либо видели его, потому что я отправил его обратно с запиской. ;-)). - person Cheers and hth. - Alf; 24.02.2015
comment
@Cheersandhth.-Alf Я чувствую, что упускаю что-то важное. что вы подразумеваете под: reinterpret_cast числовых значений? Я хотел знать, почему я не могу выполнить побитовое приведение между примитивами одного размера. В моем понимании это отвечает на что? - person Jonathan Mee; 24.02.2015
comment
@Cheersandhth.-Alf: Судя по моему прочтению вопроса, нет никакой путаницы в том, что делает reinterpret_cast и преобразует ли он значения или ссылки (при условии, что вы это имеете в виду). Он просто спрашивает, почему существуют ограничения на то, какие типы указателей/ссылок могут быть преобразованы. Кажется, я ответил на это. - person Mike Seymour; 24.02.2015
comment
@JonathanMee: «два примитива одинакового размера» в ваших примерах — это long long и double (при условии общей реализации, где они действительно имеют одинаковый размер, обычно 64 бита каждый). Это не указатели, это числовые значения. Ответ Майка касается только указателей и ссылок, немного косвенности, где есть другие проблемы. - person Cheers and hth. - Alf; 24.02.2015
comment
@Cheersandhth.-Alf Это числовые значения, это правда, но я использую reinterpret_cast для обработки foo в качестве ссылки, и, на мой взгляд, именно к этому относится этот ответ. - person Jonathan Mee; 24.02.2015
comment
@Jonathan Ну, в обоих примерах эффект, который вы реализуете (или пытаетесь реализовать), копирует биты из long long в double. Затем вы спрашиваете, почему reinterpret_cast не поддерживает это. Это привело к тому, что я неправильно истолковал то, о чем вы хотели спросить, извините. Но в любом случае обратите внимание, что основная причина так называемого строгого правила псевдонимов, объясненного Майком в этом ответе, не является причиной того, что reinterpret_cast не поддерживает переинтерпретацию значения long long как значения double. Несмотря на это, интерпретация long long как double могла (небезопасно) поддерживаться. - person Cheers and hth. - Alf; 24.02.2015
comment
@Cheersandhth.-Alf: ответ Майка касается только указателей и ссылок - нет, мой ответ касается доступа к объекту с псевдонимом через ссылку/указатель, полученный приведением. Это то, что не разрешено (дает неопределенное поведение), и (по моему чтению) это то, о чем идет речь. Но этот странный спор становится таким длинным и утомительным, что у меня возникает соблазн просто удалить ответ и притвориться, что этого никогда не было. - person Mike Seymour; 24.02.2015

В основном ограничения на reinterpret_cast (не полностью охваченные сайтом cppreference) связаны с

  • проблемы с выравниванием и
  • представления-ловушки.

Для гипотетического значения от reinterpret_cast до числа различные размеры можно легко обрабатывать с помощью усечения или нулевого расширения, поскольку в любом случае это опасная территория битового уровня, так что это не проблема.

Используя memcpy или copy_n, вы решаете проблемы с выравниванием, но вы все равно можете стать жертвой ловушек. Это означает, что использование полученного значения может взорваться. На некоторых платформах для некоторых значений.


Обратите внимание, что гарантии стандарта на что угодно могут быть и обычно расширяются любым конкретным компилятором.

Часто может быть хорошей идеей стремиться к немного меньшей переносимости, чем та, которую вы можете получить, полагаясь исключительно на стандарт.

Например, все быстро усложняется, когда вы не можете предположить, что байт равен 8 битам. Это предположение снижает переносимость. Но все же набор поддерживаемых платформ велик.

person Cheers and hth. - Alf    schedule 24.02.2015
comment
Тьфу, так ты хочешь сказать, что даже мой обходной путь не обязательно сработает? Как в нет способ детерминистически сделать это приведение? - person Jonathan Mee; 24.02.2015
comment
Различные размеры не могут быть обработаны, так как результатом приведения является указатель или ссылка на тип с псевдонимом, который ничего не знает о реальном типе объекта. Все, что он может сделать, это рассматривать все, что находится в памяти, как объект типа с псевдонимом. - person Mike Seymour; 24.02.2015
comment
@MikeSeymour: контекст важен при обсуждении вещей. Мы обсуждаем, почему reinterpret_cast не обрабатывает переинтерпретацию значений целых чисел и типов с плавающей запятой. Для этого результатом приведения является указатель или ссылка была бы некорректной (если бы такие приведения поддерживались). - person Cheers and hth. - Alf; 24.02.2015
comment
@MikeSeymour: Возможно, вы правы, поскольку я вижу, что OP использовал приведение для ссылки. Фиксация формулировки. - person Cheers and hth. - Alf; 24.02.2015
comment
@JonathanMee: Вы можете сделать приведение, но в зависимости от исходных битов вы можете на какой-то платформе получить представление-ловушку. Что может вызвать хаос, просто будучи назначенным. Тем не менее, на заданном наборе платформ (который может быть довольно большим) актерский состав может быть безопасным. Обратите внимание, что с reinterpret_cast, который у нас есть, где вы должны преобразовать в указатель или ссылку и обратно, размеры должны лучше совпадать. Основанные на значении reinterpret_cast не указателей просто не поддерживаются. - person Cheers and hth. - Alf; 24.02.2015
comment
@Cheersandhth.-Alf Хорошо, поэтому, если я правильно отслеживаю вас, ответ на мой вопрос таков: reinterpret_cast не делает copy_n для меня, потому что обработка всех возможных системных выравниваний не была возложена на компиляторы стандартом. и мне просто нужно использовать copy_n, это не гарантировано, но больше ничего нет. - person Jonathan Mee; 24.02.2015