c++ - Usar reinterpret_cast para emitir una función para anular*, ¿por qué no es ilegal?
pointers c++11 (1)
(Todas las citas son de N3337 y son equivalentes para cada borrador hasta N4296 a partir de ahí, es decir, esta respuesta es válida al menos para C ++ 11 y C ++ 14 pero no para C ++ 03 como la primera cita de esta respuesta no existe allí.)
[expr.reinterpret.cast] / 8:
La conversión de un puntero de función a un tipo de puntero de objeto o viceversa se admite de forma condicional. El significado de dicha conversión está definido por la implementación, excepto que si una implementación admite conversiones en ambas direcciones, la conversión de un valor predefinido de un tipo al otro tipo y viceversa, posiblemente con una calificación cv diferente, producirá el valor del puntero original.
Esto está contenido en su listado. Argumenta que el void
no es un tipo de objeto, pero no consideró el [número básico] básico / 3 crucial:
El tipo de puntero a
void
o un puntero a un tipo de objeto se denomina tipo de puntero de objeto .
(Es decir, un tipo de puntero de objeto no es necesariamente un "puntero a tipo de objeto"; la terminología estándar lo llevó a ese punto).
La única razón por la que
f = reinterpret_cast<decltype(f)>(s);
No está bien en GCC o Clang es que el tipo de destino, a diferencia de la expresión de origen, no está deteriorado, y claramente no se puede void*
un tipo de función. Necesitas hacer que el tipo de destino sea un puntero para que funcione, entonces funciona .
Este es un seguimiento tangencial de mi pregunta anterior La dirección de una función que coincide con una sobrecarga bool vs const void * . El contestador explicó:
El estándar [C ++ 11] no define ninguna conversión estándar de un "puntero a función" a un "puntero a
void
".Es difícil proporcionar una cotización por la ausencia de algo, pero lo más cercano que puedo hacer es C ++ 11 4.10 / 2 [conv.ptr]:
Un prvalor de tipo "puntero a cv
T
", dondeT
es un tipo de objeto , se puede convertir a un prvalor de tipo "puntero a cvvoid
". El resultado de convertir un "puntero a cvT
" en un "puntero a cvvoid
" apunta al inicio de la ubicación de almacenamiento donde reside el objeto de tipoT
, como si el objeto fuera el objeto más derivado (1.8) de tipo T (es decir, no es un subobjeto de clase base). El valor del puntero nulo se convierte al valor del puntero nulo del tipo de destino.(énfasis mío)
Suponiendo que func
se declara void func();
Si realiza una conversión de estilo C, es decir, una función (void*) func
, la conversión tendrá éxito. static_cast<void*>(func)
no es válido, pero reinterpret_cast<void*>(func)
tendrá éxito. Sin embargo, lo que no puede hacer es convertir el puntero convertido a su tipo original. Por ejemplo,
Multa:
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);
}
No bien
void func() { }
int main() {
void* s = reinterpret_cast<void*>(func);
reinterpret_cast<decltype(func)>(s);
}
N3337 comienza diciendo:
[expr.reinterpret.cast]
El resultado de la expresión
reinterpret_cast<T>(v)
es el resultado de convertir la expresiónv
al tipoT
SiT
es un tipo de referencia lvalue o una referencia rvalue al tipo de función, el resultado es un lvalue; siT
es una referencia de valor a tipo de objeto, el resultado es un valor de x; de lo contrario, el resultado es un prvalue y las conversiones estándar lvalue-to-rvalue (4.1), array-to-pointer (4.2) y function-to-pointer (4.3) se realizan en la expresión v. Conversiones que se pueden realizar explícitamente utilizandoreinterpret_cast
se enumeran a continuación. Ninguna otra conversión se puede realizar explícitamente usandoreinterpret_cast
.
Anudé el lenguaje que creo que es clave aquí. La última parte parece implicar que si la conversión no está en la lista, es ilegal. En breve resumen, las conversiones permitidas son:
- Un puntero se puede convertir explícitamente a cualquier tipo de integral lo suficientemente grande como para mantenerlo.
- Un valor de tipo integral o tipo de enumeración se puede convertir explícitamente en un puntero.
- Un puntero de función se puede convertir explícitamente en un puntero de función de un tipo diferente.
- Un puntero de objeto se puede convertir explícitamente en un puntero de objeto de un tipo diferente.
- La conversión de un puntero de función a un tipo de puntero de objeto o viceversa se admite de forma condicional.
- El valor del puntero nulo (4.10) se convierte al valor del puntero nulo del tipo de destino.
- Un prvalor de tipo "puntero a miembro de
X
de tipoT1
" se puede convertir explícitamente a un prvalor de un tipo diferente "puntero a miembro de Y de tipoT2
" siT1
yT
son ambos tipos de función o ambos tipos de objeto. - Una expresión lvalue de tipo
T1
se puede convertir al tipo "referencia aT2
" si una expresión de tipo "puntero aT1
" se puede convertir explícitamente al tipo "puntero aT2
" utilizando un reinterpret_cast.
void*
no es un puntero a función y los objetos no tienen función o tipo void.
[Tipos basicos]
Un tipo de objeto es un tipo (posiblemente cv calificado) que no es un tipo de función, no es un tipo de referencia, y no es un tipo de vacío.
Entonces, tal vez me estoy agarrando a los popotes, pero parece que reinterpret_cast<void*>(func)
es ilegal. Sin embargo, por otra parte, [expr.static.cast] / 5 dice "De lo contrario, el static_cast
realizará una de las conversiones que se enumeran a continuación. Ninguna otra conversión se realizará explícitamente utilizando un static_cast
". la diferencia clave es "debe" y "puede". ¿Es esto suficiente para que el reinterpret_cast
legal o me estoy perdiendo algo más?