Законно ли переинтерпретировать_приведение к пустоте*

Я просматривал https://en.cppreference.com/w/cpp/language/reinterpret_cast и я заметил, что он указывает допустимые типы, к которым мы всегда можем привести:

  • byte*
  • char*
  • unsigned char*

Но я не увидел void* в списке. Это упущение? В моем случае использования требуется reinterpret_cast, потому что я привожу от int** к void*. И в конечном итоге я перейду от void* обратно к int**.


person Jonathan Mee    schedule 17.04.2019    source источник
comment
он указывает допустимые типы, к которым мы всегда можем привести. Это похоже на ваше изобретение. На этой странице ничего подобного не сказано.   -  person cpplearner    schedule 17.04.2019
comment
Вам не нужен reinterpret_cast для преобразования указателя в void*. godbolt.org/z/-eIfjl   -  person Max Langhof    schedule 17.04.2019
comment
Я снова открыл вопрос, потому что не видел, что к этому относится только верхний ответ. Оставив ссылку здесь: stackoverflow.com/questions/573294/ когда использовать переинтерпретацию   -  person NathanOliver    schedule 17.04.2019
comment
@cpplearner Типы находятся в разделе «Псевдонимы типов».   -  person Jonathan Mee    schedule 17.04.2019
comment
@FrançoisAndrieux Тьфу, я знал это. Я просто забываю, как идиот. Не могли бы вы просто опубликовать это как ответ, и я приму?   -  person Jonathan Mee    schedule 17.04.2019
comment
@FrançoisAndrieux И я солгал тебе. Спасибо за публикацию. Я дал вам +1, но ответ Сержа Бальесты показался мне немного более ясным, поэтому я принял этот вариант .   -  person Jonathan Mee    schedule 18.04.2019


Ответы (3)


Всегда допустимо преобразование указателя на тип в указатель на другой тип, включая void, поэтому, если T является типом, это допустимо в C++:

T* x;
void *y = reinterpret_cast<void *>(x);

В реальном мире он никогда не используется, потому что void * — это особый случай, и вы получаете то же значение с static_cast:

void *y = static_cast<void *>(x); // equivalent to previous reinterpret_cast

(на самом деле приведенное выше преобразование является неявным и может быть записано просто как void *y = x; - спасибо Майклу Кензелу за то, что он это заметил)

Чтобы быть более точным, стандарт даже говорит в проекте n4659 для C++ 17 8.2.10 Reinterpret cast [expr.reinterpret.cast], §7

Когда prvalue v типа указателя объекта преобразуется в тип указателя объекта «указатель на cv T», результатом является static_cast<cv T*>(static_cast<cv void*>(v)).

Когда вы ссылаетесь на то, что byte и char являются единственными допустимыми типами, просто допустимо разыменование преобразованного указателя только для этих типов. void сюда не включено, потому что вы никогда не сможете разыменовать void *.


Чтобы конкретно ответить на ваш вопрос

.. Я привожу от int** к void*. И в конце концов я переведу из void* обратно в int**.

Стандарт гарантирует, что первое преобразование является стандартным (читай, неявным):

Значение prvalue типа «указатель на cv T», где T — тип объекта, может быть преобразовано в значение prvalue типа «указатель на cv void». Значение указателя (6.9.2) при этом преобразовании не изменяется.

Так что это всегда законно:

int **i = ...;
void *v = i;

Для обратного кастинга стандарт говорит (в static_cast параграфе):

Значение prvalue типа «указатель на cv1 void» может быть преобразовано в значение prvalue типа «указатель на cv2 T»,

Так это тоже законно

int **j = static_cast<int **>(v);

и стандарт гарантирует, что j == i.

person Serge Ballesta    schedule 17.04.2019
comment
Вам даже не нужно static_cast в вашем примере. Любой тип указателя объекта неявно преобразуется в void* (с эквивалентными квалификаторами cv). Вам нужно только static_cast, чтобы привести void* обратно к указателю объекта определенного типа... - person Michael Kenzel; 17.04.2019
comment
Спасибо за действительно подробный ответ ... к сожалению, это немного отличается от моего недоразумения. Как вы можете видеть из последней строки вопроса, я пытаюсь преобразовать int** в void* и обратно, что, как я полагаю, требует reinterpret_cast? - person Jonathan Mee; 18.04.2019

Эти типы освобождены от строгих правил псевдонимов. Это не означает, что они являются единственным типом, который вы можете использовать с reinterpret_cast. В случае приведения указателя объекта к другому типу указателя объекта несоблюдение требований строгих правил псевдонимов означает, что вы не можете безопасно разыменовать результат. Но вы все равно можете безопасно привести результирующий указатель обратно к исходному типу и использовать результат, как если бы это был исходный указатель.

Соответствующий раздел cppreference на reinterpret_cast:

(Любой тип указателя объекта T1* может быть преобразован в другой тип указателя объекта cv T2*. Это точно эквивалентно static_cast<cv T2*>(static_cast<cv void*>(expression)) (что означает, что если требование выравнивания T2 не более строгое, чем требование T1, значение указателя не не изменяется, и преобразование результирующего указателя обратно в его исходный тип дает исходное значение). В любом случае результирующий указатель может быть безопасно разыменован только в том случае, если это разрешено правилами псевдонимов типов)

При возврате к исходному типу AliasedType и DynamicType одинаковы, поэтому они похожи, что является первым случаем, указанным в правилах псевдонимов, где допустимо разыменование результата reinterpret_cast :

Всякий раз, когда предпринимается попытка прочитать или изменить сохраненное значение объекта типа DynamicType через glvalue типа AliasedType, поведение не определено, если не выполняется одно из следующих условий:

  • AliasedType и DynamicType похожи.
  • AliasedType — это (возможно, квалифицированный по резюме) signed или unsigned вариант DynamicType.
  • AliasedType равно std::byte, (начиная с C++17) char или unsigned char: это позволяет исследовать объектное представление любого объекта в виде массива байтов.
person François Andrieux    schedule 17.04.2019

[expr.reinterpret.cast]/7 :

Указатель объекта может быть явно преобразован в указатель объекта другого типа.

[basic.compound]/3:

Тип указателя на cv void или указатель на тип объекта называется типом указателя объекта.

Однако вам не нужно использовать reinterpret_cast. Каждый тип указателя на объект, чей указанный тип является cv-unqualified, неявно преобразуется в void*, а обратное может быть сделано с помощью static_cast.

person cpplearner    schedule 17.04.2019
comment
Спасибо за действительно подробный ответ ... к сожалению, это немного отличается от моего недоразумения. Как вы можете видеть из последней строки вопроса, я пытаюсь преобразовать int** в void*, что, как я полагаю, требует reinterpret_cast? - person Jonathan Mee; 18.04.2019
comment
Нет? Указатель на int* является указателем на тип объекта cv-unqualified. - person cpplearner; 18.04.2019
comment
Отмените это, это приведение от void* к int**, для которого требуется reinterpret_cast. Вы согласны с этим, верно? Я пиратил пример этого ответа, чтобы продемонстрировать: ideone.com/G4zuwd Если вы не знаете чего-то, чего не знаю я, мне понадобится reinterpret_cast, чтобы назначить ptr, верно? - person Jonathan Mee; 18.04.2019