¿Se puede convertir nullptr a uintptr_t? Diferentes compiladores no están de acuerdo

10

Considere este programa:

#include <cstdint>
using my_time_t = uintptr_t;

int main() {
    const my_time_t t = my_time_t(nullptr);
}

No se pudo compilar con msvc v19.24:

<source>(5): error C2440: '<function-style-cast>': cannot convert from 'nullptr' to 'my_time_t'
<source>(5): note: A native nullptr can only be converted to bool or, using reinterpret_cast, to an integral type
<source>(5): error C2789: 't': an object of const-qualified type must be initialized
<source>(5): note: see declaration of 't'

Compiler returned: 2

pero clang (9.0.1) y gcc (9.2.1) "comen" este código sin ningún error.

Me gusta el comportamiento de MSVC, pero ¿está confirmado por estándar? En otras palabras, ¿es un error en clang / gcc o es posible interpretar estándar que este es el comportamiento correcto de gcc / clang?

usuario1244932
fuente
2
Leí esto como una inicialización de copia de un elenco de estilo funcional. Luego, el compilador lo interpreta como uno de los lanzamientos de C ++ "incluso si no se puede compilar". Tal vez haya una inconsistencia entre los compiladores en cuanto a cómo se interpreta el elenco
wreckgar23
Hasta donde sé, MSVC v19.24 no es compatible con un modo C ++ 11. ¿Quiso decir C ++ 14 o C ++ 17 en su lugar?
nogal

Respuestas:

5

En mi opinión, MSVC no se comporta conforme a la norma.

Estoy basando esta respuesta en C ++ 17 (borrador N4659), pero C ++ 14 y C ++ 11 tienen una redacción equivalente.

my_time_t(nullptr)es una expresión postfix y debido a que my_time_tes un tipo y (nullptr)es una expresión única en una lista de inicializadores entre paréntesis, es exactamente equivalente a una expresión de conversión explícita. ( [expr.type.conv] / 2 )

El reparto explícito intenta con algunos cambios específicos de C ++ (con extensiones), en particular también reinterpret_cast. ( [expr.cast] /4.4 ) Los lanzamientos que se probaron anteriormente reinterpret_castson const_casty static_cast(con extensiones y también en combinación), pero ninguno de estos puede lanzarse std::nullptr_ta un tipo integral.

Pero reinterpret_cast<my_time_t>(nullptr)debería tener éxito porque [expr.reinterpret.cast] / 4 dice que un valor de tipo std::nullptr_tse puede convertir a un tipo integral como si fuera by reinterpret_cast<my_time_t>((void*)0), lo cual es posible porque my_time_t = std::uintptr_tdebería ser un tipo lo suficientemente grande como para representar todos los valores de puntero y bajo esta condición el El mismo párrafo estándar permite la conversión void*a un tipo integral.

Es particularmente extraño que MSVC permita la conversión si se utiliza la notación de conversión en lugar de la notación funcional:

const my_time_t t = (my_time_t)nullptr;
nuez
fuente
1
Sí. Tenga static_casten cuenta que, en particular, algunos casos tienen la intención de atrapar la escalera de lanzamiento de estilo C (por ejemplo, un lanzamiento de estilo C a una base ambigua es un mal formado en static_castlugar de un reinterpret_cast), pero ninguno se aplica aquí.
TC
my_time_t(nullptr)es, por definición, el mismo que (my_time_t)nullptr, por lo que MSVC ciertamente está equivocado al aceptar uno y rechazar el otro.
Richard Smith
2

Aunque no puedo encontrar ninguna mención explícita en este Borrador de Trabajo Estándar C ++ (de 2014) que la conversión de std::nullptr_ta un tipo integral esté prohibida, ¡tampoco se menciona que tal conversión esté permitida!

Sin embargo, el caso de conversión de std::nullptr_ta bool se menciona explícitamente:

4.12 Conversiones booleanas
Un valor de aritmética, enumeración sin alcance, puntero o puntero a tipo de miembro se puede convertir a un valor de tipo bool. Un valor cero, un valor de puntero nulo o un valor de puntero de miembro nulo se convierte en falso; cualquier otro valor se convierte en verdadero. Para la inicialización directa (8.5), un valor prva de tipo std :: nullptr_t se puede convertir en un valor prva de tipo bool; El valor resultante es falso.

Además, el único lugar en este documento borrador donde se menciona la conversión de std::nullptr_ta un tipo integral, es en la sección "reinterpret_cast":

5.2.10 Reinterpret cast
...
(4) Un puntero se puede convertir explícitamente a cualquier tipo integral lo suficientemente grande como para contenerlo. La función de mapeo está definida por la implementación. [Nota: no pretende sorprender a quienes conocen la estructura de direccionamiento de la máquina subyacente. - nota final] Un valor de tipo std :: nullptr_t puede convertirse en un tipo integral; la conversión tiene el mismo significado y validez que una conversión de (void *) 0 al tipo integral. [Nota: un reinterpret_cast no se puede usar para convertir un valor de ningún tipo al tipo std :: nullptr_t. - nota final]

Entonces, a partir de estas dos observaciones, uno podría (IMHO) suponer razonablemente que el MSVCcompilador es correcto.

EDITAR : Sin embargo, su uso del "reparto de notación funcional" en realidad puede sugerir lo contrario. El MSVCcompilador no tiene ningún problema al usar una conversión de estilo C, por ejemplo:

uintptr_t answer = (uintptr_t)(nullptr);

pero (como en su código), se queja de esto:

uintptr_t answer = uintptr_t(nullptr); // error C2440: '<function-style-cast>': cannot convert from 'nullptr' to 'uintptr_t'

Sin embargo, del mismo Proyecto de Norma:

5.2.3 Conversión de tipo explícita (notación funcional)
(1) Un especificador de tipo simple (7.1.6.2) o un especificador de nombre de tipo (14.6) seguido de una lista de expresiones entre paréntesis construye un valor del tipo especificado dada la lista de expresiones. Si la lista de expresiones es una sola expresión, la expresión de conversión de tipo es equivalente (en definición, y si está definida en significado) a la expresión de conversión correspondiente (5.4). ...

La "expresión de conversión correspondiente (5.4)" puede referirse a una conversión de estilo C.

Adrian Mole
fuente
0

Todos son estándar (ref. Draft n4659 para C ++).

nullptr se define en [lex.nullptr] como:

El puntero literal es la palabra clave nullptr. Es un valor de tipo std :: nullptr_t. [Nota: ..., un prvalue de este tipo es una constante de puntero nulo y puede convertirse en un valor de puntero nulo o un valor de puntero de miembro nulo.]

Incluso si las notas no son normativas, esta deja en claro que para el estándar, nullptrse espera que se convierta en un valor de puntero nulo .

Más tarde encontramos en [conv.ptr]:

Una constante de puntero nulo es un literal entero con valor cero o un valor de tipo std :: nullptr_t. Una constante de puntero nulo se puede convertir a un tipo de puntero; .... Una constante de puntero nulo de tipo integral se puede convertir a un prvalor de tipo std :: nullptr_t.

Una vez más, lo que requiere el estándar es que 0se puede convertir a ay std::nullptr_tque nullptrse puede convertir a cualquier tipo de puntero.

Mi lectura es que el estándar no tiene ningún requisito sobre si nullptrse puede convertir directamente a un tipo integral o no. Desde ese punto en adelante:

  • MSVC tiene una lectura estricta y prohíbe la conversión
  • Clang y gcc se comportan como si void *estuviera involucrada una conversión intermedia .
Serge Ballesta
fuente
1
Creo que esto está mal. Algunas constantes de puntero nulo son literales enteros con valor cero, pero nullptrno porque tenga el tipo no integral std::nullptr_t. 0 se puede convertir a un std::nullptr_tvalor, pero no al literal nullptr. Todo esto es intencional, std::nullptr_tes un tipo más restringido para evitar conversiones no deseadas.
MSalters
@MSalters: Creo que tienes razón. Quería reformularlo y lo hice mal. He editado mi publicación con tu comentario. Gracias por tu ayuda.
Serge Ballesta