Considere este programa:
#include <cstdint>
using my_time_t = uintptr_t;
int main() {
const my_time_t t = my_time_t(nullptr);
}
Falha ao compilar com o 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
mas clang (9.0.1) e gcc (9.2.1) "comem" esse código sem erros.
Eu gosto do comportamento do MSVC, mas é confirmado por padrão? Em outras palavras, é bug no clang / gcc ou é possível interpretar o padrão de que esse é o comportamento correto do gcc / clang?
Respostas:
Na minha opinião, o MSVC não está se comportando em conformidade com os padrões.
Estou baseando essa resposta no C ++ 17 (rascunho N4659), mas o C ++ 14 e o C ++ 11 têm uma redação equivalente.
my_time_t(nullptr)
é uma expressão postfix e, comomy_time_t
é um tipo e(nullptr)
é uma expressão única em uma lista de inicializadores entre parênteses, é exatamente equivalente a uma expressão de conversão explícita. ( [expr.type.conv] / 2 )A conversão explícita tenta algumas conversões C ++ específicas diferentes (com extensões), em particular também
reinterpret_cast
. ( [expr.cast] /4.4 ) Os elencos tentados anteriormentereinterpret_cast
sãoconst_cast
estatic_cast
(com extensões e também em combinação), mas nenhum deles pode ser convertidostd::nullptr_t
em um tipo integral.Mas
reinterpret_cast<my_time_t>(nullptr)
deve ter sucesso porque [expr.reinterpret.cast] / 4 diz que um valor do tipostd::nullptr_t
pode ser convertido em um tipo integral como se fossereinterpret_cast<my_time_t>((void*)0)
, o que é possível porquemy_time_t = std::uintptr_t
deve ser um tipo grande o suficiente para representar todos os valores do ponteiro e, sob essa condição, o valor mesmo parágrafo padrão permite a conversão devoid*
para um tipo integral.É particularmente estranho que o MSVC permita a conversão se for usada notação de conversão em vez de notação funcional:
fonte
static_cast
em particular, alguns casos pretendem interceptar a escada de conversão em estilo C (por exemplo, uma conversão em estilo C para uma base ambígua é mais malformadastatic_cast
do que umareinterpret_cast
), mas nenhuma se aplica aqui.my_time_t(nullptr)
é, por definição, o mesmo que(my_time_t)nullptr
, portanto, a MSVC certamente está errada ao aceitar um e rejeitar o outro.Embora eu não encontre menção explícita neste Padrão de Trabalho C ++ (de 2014) de que a conversão de
std::nullptr_t
para um tipo integral é proibida, também não há menção de que essa conversão seja permitida!No entanto, o caso de conversão de
std::nullptr_t
parabool
é mencionado explicitamente:Além disso, o único lugar neste documento de rascunho em que a conversão de
std::nullptr_t
para um tipo integral é mencionada é na seção "reinterpret_cast":Portanto, a partir dessas duas observações, pode-se (IMHO) razoavelmente supor que o
MSVC
compilador esteja correto.EDIT : No entanto, o uso do "elenco de notação funcional" pode realmente sugerir o contrário! O
MSVC
compilador não tem nenhum problema ao usar uma conversão no estilo C, por exemplo:mas (como no seu código), ele reclama disso:
No entanto, do mesmo Projeto de Norma:
A "expressão de conversão correspondente (5.4)" pode se referir a uma conversão no estilo C.
fonte
Todos são conformes padrão (ref. Rascunho n4659 para C ++).
nullptr
é definido em [lex.nullptr] como:Mesmo que as notas não sejam normativas, esta deixa claro que, para o padrão,
nullptr
espera-se que seja convertido em um valor de ponteiro nulo .Mais tarde, encontramos em [conv.ptr]:
Aqui, novamente, o que é exigido pelo padrão é que
0
pode ser convertido em astd::nullptr_t
e quenullptr
pode ser convertido para qualquer tipo de ponteiro.Minha leitura é que o padrão não exige se
nullptr
pode ser convertido diretamente em um tipo integral ou não. A partir desse momento:void *
conversão intermediária estivesse envolvida.fonte
nullptr
não é porque tem o tipo não integralstd::nullptr_t
. 0 pode ser convertido em umstd::nullptr_t
valor, mas não no literalnullptr
. Tudo isso é intencional,std::nullptr_t
é um tipo mais restrito para evitar conversões não intencionais.