c++ pointers c++11 language-lawyer

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 ", donde T es un tipo de objeto , se puede convertir a un prvalor de tipo "puntero a cv void ". El resultado de convertir un "puntero a cv T " en un "puntero a cv void " apunta al inicio de la ubicación de almacenamiento donde reside el objeto de tipo T , 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ón v al tipo T Si T es un tipo de referencia lvalue o una referencia rvalue al tipo de función, el resultado es un lvalue; si T 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 utilizando reinterpret_cast se enumeran a continuación. Ninguna otra conversión se puede realizar explícitamente usando reinterpret_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 tipo T1 " se puede convertir explícitamente a un prvalor de un tipo diferente "puntero a miembro de Y de tipo T2 " si T1 y T son ambos tipos de función o ambos tipos de objeto.
  • Una expresión lvalue de tipo T1 se puede convertir al tipo "referencia a T2 " si una expresión de tipo "puntero a T1 " se puede convertir explícitamente al tipo "puntero a T2 " 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?