Nullptr può essere convertito in uintptr_t? Diversi compilatori non sono d'accordo


10

Considera questo programma:

#include <cstdint>
using my_time_t = uintptr_t;

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

Impossibile compilare 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

ma clang (9.0.1) e gcc (9.2.1) "mangiano" questo codice senza errori.

Mi piace il comportamento di MSVC, ma è confermato dallo standard? In altre parole è un bug in clang / gcc o è possibile interpretare lo standard che questo è il comportamento corretto da gcc / clang?


2
Ho letto questo come inizializzazione della copia da un cast in stile funzione. Ciò viene quindi interpretato dal compilatore come uno dei cast di C ++ "anche se non può essere compilato". Forse c'è un'incongruenza tra i compilatori su come viene interpretato il cast
wreckgar23

Per quanto ne so MSVC v19.24 non supporta una modalità C ++ 11. Intendi invece C ++ 14 o C ++ 17?
noce

Risposte:


5

A mio avviso, MSVC non si comporta in modo conforme agli standard.

Sto basando questa risposta su C ++ 17 (bozza N4659), ma C ++ 14 e C ++ 11 hanno una formulazione equivalente.

my_time_t(nullptr)è un'espressione postfissa e poiché my_time_tè un tipo ed (nullptr)è una singola espressione in un elenco di inizializzatori tra parentesi, è esattamente equivalente a un'espressione di cast esplicita. ( [expr.type.conv] / 2 )

Il cast esplicito prova alcuni cast specifici diversi C ++ (con estensioni), in particolare anche reinterpret_cast. ( [expr.cast] /4.4 ) I cast provati prima reinterpret_castsono const_caste static_cast(con estensioni e anche in combinazione), ma nessuno di questi può essere castato std::nullptr_tin un tipo integrale.

Ma reinterpret_cast<my_time_t>(nullptr)dovrebbe avere successo perché [expr.reinterpret.cast] / 4 dice che un valore di tipo std::nullptr_tpuò essere convertito in un tipo integrale come se da reinterpret_cast<my_time_t>((void*)0), il che è possibile perché my_time_t = std::uintptr_tdovrebbe essere un tipo abbastanza grande da rappresentare tutti i valori del puntatore e in questa condizione il lo stesso paragrafo standard consente la conversione void*in un tipo integrale.

È particolarmente strano che MSVC consenta la conversione se viene utilizzata la notazione di cast piuttosto che la notazione funzionale:

const my_time_t t = (my_time_t)nullptr;

1
Sì. Si noti che, static_castin particolare, alcuni casi intendono intrappolare la scala del cast in stile C (ad esempio, un cast in stile C su una base ambigua è un mal formato static_castpiuttosto che un reinterpret_cast), ma nessuno si applica qui.
TC

my_time_t(nullptr)è per definizione lo stesso di (my_time_t)nullptr, quindi MSVC ha sicuramente torto ad accettarne uno e rifiutarne l'altro.
Richard Smith,

2

Anche se non trovo alcuna menzione esplicita in questo Working Craft Standard C ++ (dal 2014) che la conversione da std::nullptr_tun tipo integrale è vietata, non si fa menzione del fatto che tale conversione è consentita!

Tuttavia, il caso della conversione da std::nullptr_ta bool viene esplicitamente menzionato:

4.12 Conversioni booleane
Un valore di aritmetica, enumerazione senza ambito, puntatore o puntatore al tipo di membro può essere convertito in un valore di tipo bool. Un valore zero, un valore del puntatore nullo o un valore del puntatore del membro null viene convertito in false; qualsiasi altro valore viene convertito in vero. Per l'inizializzazione diretta (8.5), un valore di tipo std :: nullptr_t può essere convertito in un valore di tipo bool; il valore risultante è falso.

Inoltre, l' unico posto in questo progetto di documento in cui std::nullptr_tè menzionata la conversione da un tipo integrale, è nella sezione "reinterpret_cast":

5.2.10 Reinterpretare il cast
...
(4) Un puntatore può essere esplicitamente convertito in qualsiasi tipo integrale abbastanza grande da mantenerlo. La funzione di mappatura è definita dall'implementazione. [Nota: si intende che non sorprenda chi conosce la struttura di indirizzamento della macchina sottostante. - end note] Un valore di tipo std :: nullptr_t può essere convertito in un tipo integrale; la conversione ha lo stesso significato e validità di una conversione di (void *) 0 nel tipo integrale. [Nota: un reinterpret_cast non può essere utilizzato per convertire un valore di qualsiasi tipo nel tipo std :: nullptr_t. - nota finale]

Quindi, da queste due osservazioni, si potrebbe ragionevolmente supporre (IMHO) che il MSVCcompilatore sia corretto.

EDIT : Tuttavia, l'uso del "cast di notazione funzionale" può effettivamente suggerire il contrario! Il MSVCcompilatore non ha problemi a utilizzare un cast in stile C, ad esempio:

uintptr_t answer = (uintptr_t)(nullptr);

ma (come nel tuo codice), si lamenta di questo:

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

Tuttavia, dallo stesso Draft Standard:

5.2.3 Conversione di tipo esplicita (notazione funzionale)
(1) Un identificatore di tipo semplice (7.1.6.2) o un identificatore di nome (14.6) seguito da un elenco di espressioni tra parentesi costruisce un valore del tipo specificato dato l'elenco di espressioni. Se l'elenco di espressioni è una singola espressione, l'espressione di conversione del tipo è equivalente (nella definizione e se definita nel significato) all'espressione di cast corrispondente (5.4). ...

L'espressione "cast corrispondente" (5.4) "può fare riferimento a un cast in stile C.


0

Tutti sono conformi allo standard (rif. Bozza n4659 per C ++).

nullptr è definito in [lex.nullptr] come:

Il puntatore letterale è la parola chiave nullptr. È un valore di tipo std :: nullptr_t. [Nota: ..., un valore di questo tipo è una costante puntatore null e può essere convertito in un valore puntatore null o valore puntatore null membro.]

Anche se le note non sono normative, questo chiarisce che per lo standard nullptrdovrebbe essere convertito in un valore puntatore nullo .

In seguito troviamo in [conv.ptr]:

Una costante puntatore null è un valore intero letterale con valore zero o un valore di tipo std :: nullptr_t. Una costante di puntatore null può essere convertita in un tipo di puntatore; .... Una costante puntatore null di tipo integrale può essere convertita in un valore di tipo std :: nullptr_t.

Anche in questo caso ciò che è richiesto dallo standard è che 0può essere convertito in un std::nullptr_te che nullptrpuò essere convertito in qualsiasi tipo di puntatore.

La mia lettura è che lo standard non ha alcun requisito sul fatto che nullptrpossa essere convertito direttamente in un tipo integrale o meno. Da quel punto in poi:

  • MSVC ha una lettura rigorosa e proibisce la conversione
  • Clang e gcc si comportano come se void *fosse coinvolta una conversione intermedia .

1
Penso che sia sbagliato. Alcune costanti del puntatore null sono valori letterali interi con valore zero, ma nullptrnon perché hanno un tipo non integrale std::nullptr_t. 0 può essere convertito in un std::nullptr_tvalore, ma non in letterale nullptr. Tutto questo è intenzionale, std::nullptr_tè un tipo più limitato per prevenire conversioni indesiderate.
MSalters

@MSalters: penso che tu abbia ragione. Volevo riformularlo e ho sbagliato. Ho modificato il mio post con il tuo commento. Grazie per l'aiuto.
Serge Ballesta
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.