Использование 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
закон или я пропущу что-то еще?
Ответы
Ответ 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*
от типа функции. Вам нужно сделать целевой тип указателем на функцию, , затем он работает.