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?

Respuestas:
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 quemy_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 anteriormentereinterpret_castsonconst_castystatic_cast(con extensiones y también en combinación), pero ninguno de estos puede lanzarsestd::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 tipostd::nullptr_tse puede convertir a un tipo integral como si fuera byreinterpret_cast<my_time_t>((void*)0), lo cual es posible porquemy_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ónvoid*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:
fuente
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 enstatic_castlugar de unreinterpret_cast), pero ninguno se aplica aquí.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.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_taboolse menciona explícitamente: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":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:pero (como en su código), se queja de esto:
Sin embargo, del mismo Proyecto de Norma:
La "expresión de conversión correspondiente (5.4)" puede referirse a una conversión de estilo C.
fuente
Todos son estándar (ref. Draft n4659 para C ++).
nullptrse define en [lex.nullptr] como: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 vez más, lo que requiere el estándar es que
0se puede convertir a aystd::nullptr_tquenullptrse 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:void *estuviera involucrada una conversión intermedia .fuente
nullptrno porque tenga el tipo no integralstd::nullptr_t. 0 se puede convertir a unstd::nullptr_tvalor, pero no al literalnullptr. Todo esto es intencional,std::nullptr_tes un tipo más restringido para evitar conversiones no deseadas.