Подтвердить что ты не робот

Использование reinterpret_cast для включения функции в void *, почему это не является незаконным?

Это тангенциальное продолжение моего предыдущего вопроса Адрес функции, соответствующей перегрузке bool vs const void *. Ответчик объяснил:

Стандарт [С++ 11] не определяет стандартные преобразования из "указатель на функцию" на "указатель на void".

Трудно представить цитату из-за отсутствия чего-то, но ближайший я могу сделать это С++ 11 4.10/2 [conv.ptr]:

Значение типа "указатель на cv T", , где T является типом объекта, может быть преобразовано в значение prvalue типа "указатель на cv    void". Результат преобразования указателя на cv T "на   " указатель на cv void" указывает на начало места хранения   где объект типа T находится, как если бы объект был наиболее   (1.8) типа T (т.е. не подобъект базового класса).   Значение нулевого указателя преобразуется в значение нулевого указателя   тип назначения.

(акцент мой)

Предполагая, что func объявлен void func();, если вы выполняете C-стиль, т.е. (void*) func, приведение будет успешным. static_cast<void*>(func) однако недействителен, но reinterpret_cast<void*>(func) будет успешным. Однако вы не можете сделать преобразованный преобразованный указатель обратно в исходный. Например,

Fine:

int main() {
  int* i;
  void* s = static_cast<void*>(i);
  i = static_cast<int*>(s);
  s = reinterpret_cast<void*>(i);
  i = reinterpret_cast<int*>(s);
}

Неплохо:

void func() { }

int main() {
  void* s = reinterpret_cast<void*>(func);
  reinterpret_cast<decltype(func)>(s);
}

N3337 начинается, говоря:

[expr.reinterpret.cast]

Результат выражения reinterpret_cast<T>(v) является результатом преобразуя выражение v в тип T. Если T - значение lvalue ссылочного типа или ссылки на тип функции, результат lvalue; если T является ссылкой rvalue на тип объекта, результатом является значение x; в противном случае результатом является значение prvalue и значение lvalue-to-rvalue (4.1), от массива к указателю (4.2) и стандартного стандарта (4.3) преобразования выполняются над выражением v. Конверсии, которые могут быть выполняемые явно с использованием reinterpret_cast, перечислены ниже. нет другое преобразование может быть выполнено явно с помощью reinterpret_cast.

Я выделил язык, который, по моему мнению, является ключевым. Последняя часть, по-видимому, подразумевает, что если конверсия отсутствует в списке, она является незаконной. Вкратце, разрешенные преобразования:

  • Указатель может быть явно преобразован в любой целочисленный тип, достаточно большой для его хранения.
  • Значение типа интегрального типа или перечисления может быть явно преобразовано в указатель.
  • Указатель функции может быть явно преобразован в указатель функции другого типа.
  • Указатель объекта может быть явно преобразован в указатель объекта другого типа.
  • Преобразование указателя функции в тип указателя объекта или наоборот условно поддерживается.
  • Значение нулевого указателя (4.10) преобразуется в значение нулевого указателя для типа назначения.
  • Значение prawue типа "указатель на член X типа T1" может быть явно преобразовано в prvalue другого типа "указатель на элемент Y типа T2", если T1 и T 2 - оба типа функций или оба типа объектов.
  • Выражение lvalue типа T1 может быть передано типу "ссылка на T2", если выражение типа "указатель на T1" может быть явно преобразовано в тип "указатель на T2" используя reinterpret_cast.

void* не является указателем на функцию, а объекты не имеют функции или типа void.

[basic.types]

Тип объекта - это (возможно, cv-квалифицированный) тип, который не является тип функции, а не тип ссылки, а не тип void.

Так что, возможно, я хватаюсь за соломинку, но кажется, что reinterpret_cast<void*>(func) является незаконным. Однако, с другой стороны, [expr.static.cast]/5 говорит: "В противном случае static_cast должен выполнить одно из перечисленных ниже преобразований. выполняется явным образом с помощью static_cast. "Ключевое различие заключается в том, что" должен "и" может ". Достаточно ли сделать законный reinterpret_cast закон или я пропущу что-то еще?

4b9b3361

Ответ 1

(Все кавычки от N3337 и эквивалентны для каждого отдельного проекта до N4296 оттуда, то есть этот ответ действителен, по крайней мере, для С++ 11 и С++ 14 , но не для C + +03, поскольку первая цитата этого ответа там не существует.)

[expr.reinterpret.cast]/8:

Преобразование указателя на тип указателя объекта или наоборот условно поддерживается. Значение такого преобразования определяется реализацией, за исключением того, что если реализация поддерживает преобразования в обоих направлениях, преобразование значения одного типа в другой тип и обратно, возможно с различной cv-квалификацией, должен давать значение указателя оригнала.

Это содержится в вашем листинге. Вы утверждаете, что void не является типом объекта, но вы не рассматривали ключевое [basic.compound]/3:

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

(То есть тип указателя объекта необязательно является "указателем на тип объекта" - стандартная терминология доставила вас туда.)

Единственная причина, по которой

f = reinterpret_cast<decltype(f)>(s);

Неплохо в GCC или Clang заключается в том, что целевой тип, в отличие от исходного выражения, не разлагается - и вы можете явно не отличать void* от типа функции. Вам нужно сделать целевой тип указателем на функцию, , затем он работает.