O nullptr pode ser convertido em uintptr_t? Compiladores diferentes discordam

10

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?

user1244932
fonte
2
Eu li isso como inicialização de cópia de uma conversão no estilo de função. Isso é interpretado pelo compilador como um dos lançamentos em C ++ "mesmo que não possa ser compilado". Talvez haja uma inconsistência entre os compiladores sobre como o elenco é interpretado
wreckgar23
Tanto quanto sei, o MSVC v19.24 não oferece suporte ao modo C ++ 11. Você quis dizer C ++ 14 ou C ++ 17?
noz

Respostas:

5

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, como my_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 anteriormente reinterpret_castsão const_caste static_cast(com extensões e também em combinação), mas nenhum deles pode ser convertido std::nullptr_tem um tipo integral.

Mas reinterpret_cast<my_time_t>(nullptr)deve ter sucesso porque [expr.reinterpret.cast] / 4 diz que um valor do tipo std::nullptr_tpode ser convertido em um tipo integral como se fosse reinterpret_cast<my_time_t>((void*)0), o que é possível porque my_time_t = std::uintptr_tdeve 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 de void*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:

const my_time_t t = (my_time_t)nullptr;
noz
fonte
11
Sim. Observe que, static_castem 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 malformada static_castdo que uma reinterpret_cast), mas nenhuma se aplica aqui.
TC
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.
Richard Smith
2

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_tpara 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_tpara bool é mencionado explicitamente:

4.12 Conversões booleanas
Um pré-valor de aritmética, enumeração sem escopo, ponteiro ou ponteiro para o tipo de membro pode ser convertido em um pré-valor do tipo bool. Um valor zero, um valor de ponteiro nulo ou um valor de ponteiro de membro nulo é convertido em falso; qualquer outro valor é convertido em true. Para inicialização direta (8.5), um pré-valor do tipo std :: nullptr_t pode ser convertido em um pré-valor do tipo bool; o valor resultante é falso.

Além disso, o único lugar neste documento de rascunho em que a conversão de std::nullptr_tpara um tipo integral é mencionada é na seção "reinterpret_cast":

5.2.10 Reinterpretar a conversão
...
(4) Um ponteiro pode ser convertido explicitamente em qualquer tipo integral grande o suficiente para segurá-lo. A função de mapeamento é definida pela implementação. [Nota: O objetivo é não surpreender quem conhece a estrutura de endereçamento da máquina subjacente. - nota final] Um valor do tipo std :: nullptr_t pode ser convertido em um tipo integral; a conversão tem o mesmo significado e validade que uma conversão de (vazio *) 0 para o tipo integral. [Nota: Um reinterpret_cast não pode ser usado para converter um valor de qualquer tipo no tipo std :: nullptr_t. - nota final]

Portanto, a partir dessas duas observações, pode-se (IMHO) razoavelmente supor que o MSVCcompilador esteja correto.

EDIT : No entanto, o uso do "elenco de notação funcional" pode realmente sugerir o contrário! O MSVCcompilador não tem nenhum problema ao usar uma conversão no estilo C, por exemplo:

uintptr_t answer = (uintptr_t)(nullptr);

mas (como no seu código), ele reclama disso:

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

No entanto, do mesmo Projeto de Norma:

5.2.3 Conversão explícita de tipos (notação funcional)
(1) Um especificador de tipo simples (7.1.6.2) ou especificador de nome de tipo (14.6) seguido por uma lista de expressões entre parênteses constrói um valor do tipo especificado, dada a lista de expressões. Se a lista de expressões for uma expressão única, a expressão de conversão de tipo será equivalente (em definição e se definida em significado) à expressão de conversão correspondente (5.4). ...

A "expressão de conversão correspondente (5.4)" pode se referir a uma conversão no estilo C.

Adrian Mole
fonte
0

Todos são conformes padrão (ref. Rascunho n4659 para C ++).

nullptr é definido em [lex.nullptr] como:

O literal do ponteiro é a palavra-chave nullptr. É um pré-valor do tipo std :: nullptr_t. [Nota: ..., um pré-valor desse tipo é uma constante de ponteiro nulo e pode ser convertido em um valor de ponteiro nulo ou em um valor de ponteiro de membro nulo.]

Mesmo que as notas não sejam normativas, esta deixa claro que, para o padrão, nullptrespera-se que seja convertido em um valor de ponteiro nulo .

Mais tarde, encontramos em [conv.ptr]:

Uma constante de ponteiro nulo é um literal inteiro com valor zero ou um prvalor do tipo std :: nullptr_t. Uma constante de ponteiro nulo pode ser convertida em um tipo de ponteiro; .... Uma constante de ponteiro nulo do tipo integral pode ser convertida em um pré-valor do tipo std :: nullptr_t.

Aqui, novamente, o que é exigido pelo padrão é que 0pode ser convertido em a std::nullptr_te que nullptrpode ser convertido para qualquer tipo de ponteiro.

Minha leitura é que o padrão não exige se nullptrpode ser convertido diretamente em um tipo integral ou não. A partir desse momento:

  • MSVC tem uma leitura estrita e proíbe a conversão
  • Clang e gcc se comportam como se uma void *conversão intermediária estivesse envolvida.
Serge Ballesta
fonte
11
Acho que isso está errado. Algumas constantes de ponteiro nulo são literais inteiros com valor zero, mas nullptrnão é porque tem o tipo não integral std::nullptr_t. 0 pode ser convertido em um std::nullptr_tvalor, mas não no literal nullptr. Tudo isso é intencional, std::nullptr_té um tipo mais restrito para evitar conversões não intencionais.
MSalters 3/03
@MSalters: Eu acho que você está certo. Eu queria reformular e fazer errado. Eu editei minha postagem com seu comentário. Obrigado pela ajuda.
Serge Ballesta