Kann nullptr in uintptr_t konvertiert werden? Verschiedene Compiler sind sich nicht einig

10

Betrachten Sie dieses Programm:

#include <cstdint>
using my_time_t = uintptr_t;

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

Es konnte nicht mit msvc v19.24 kompiliert werden:

<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

aber clang (9.0.1) und gcc (9.2.1) "essen" diesen Code ohne Fehler.

Ich mag das MSVC-Verhalten, aber wird es standardmäßig bestätigt? Mit anderen Worten, ist es ein Fehler in clang / gcc oder ist es möglich, den Standard so zu interpretieren, dass dies das richtige Verhalten von gcc / clang ist?

user1244932
quelle
2
Ich habe dies als Kopierinitialisierung aus einer Besetzung im Funktionsstil gelesen. Das wird dann vom Compiler als einer der C ++ - Casts interpretiert, "auch wenn es nicht kompiliert werden kann". Vielleicht gibt es eine Inkonsistenz zwischen den Compilern
hinsichtlich der
Soweit mir bekannt ist, unterstützt MSVC v19.24 keinen C ++ 11-Modus. Meinten Sie stattdessen C ++ 14 oder C ++ 17?
Walnuss

Antworten:

5

Meiner Meinung nach verhält sich MSVC nicht standardkonform.

Ich stütze diese Antwort auf C ++ 17 (Entwurf N4659), aber C ++ 14 und C ++ 11 haben den gleichen Wortlaut.

my_time_t(nullptr)ist ein Postfix-Ausdruck und da my_time_tes sich um einen Typ und (nullptr)einen einzelnen Ausdruck in einer Initialisierungsliste in Klammern handelt, entspricht er genau einem expliziten Cast-Ausdruck. ( [expr.type.conv] / 2 )

Die explizite Besetzung versucht insbesondere auch einige verschiedene spezifische C ++ - Besetzungen (mit Erweiterungen) reinterpret_cast. ( [expr.cast] /4.4 ) Die zuvor versuchten Casts reinterpret_castsind const_castund static_cast(mit Erweiterungen und auch in Kombination), aber keiner von diesen kann std::nullptr_tzu einem integralen Typ gegossen werden .

Sollte aber erfolgreich reinterpret_cast<my_time_t>(nullptr)sein, weil [expr.reinterpret.cast] / 4 besagt, dass ein Wert vom Typ std::nullptr_twie von in einen ganzzahligen Typ konvertiert werden kann reinterpret_cast<my_time_t>((void*)0), was möglich ist, weil my_time_t = std::uintptr_tein Typ groß genug sein sollte, um alle Zeigerwerte darzustellen, und unter dieser Bedingung der Der gleiche Standardabsatz ermöglicht die Konvertierung void*in einen integralen Typ.

Es ist besonders seltsam, dass MSVC die Konvertierung zulässt, wenn die Cast-Notation anstelle der funktionalen Notation verwendet wird:

const my_time_t t = (my_time_t)nullptr;
Nussbaum
quelle
1
Ja. Beachten Sie, dass static_castinsbesondere in einigen Fällen die Gussleiter im C-Stil abgefangen werden soll (z. B. ist ein Guss im C-Stil auf eine mehrdeutige Basis eher eine Fehlform static_castals eine reinterpret_cast), aber hier gilt keine.
TC
my_time_t(nullptr)ist per Definition dasselbe wie (my_time_t)nullptr, also ist MSVC sicherlich falsch, das eine zu akzeptieren und das andere abzulehnen.
Richard Smith
2

Obwohl ich in diesem Arbeitsentwurf C ++ Standard (ab 2014) nicht ausdrücklich erwähnen kann, dass die Konvertierung von einem integralen Typ verboten ist, gibt es auch keine Erwähnung, dass eine solche Konvertierung zulässig ist!std::nullptr_t

Der Fall der Umstellung von std::nullptr_tauf bool wird jedoch ausdrücklich erwähnt:

4.12 Boolesche Konvertierungen
Ein Wert von arithmetischer, nicht skalierter Aufzählung, Zeiger oder Zeiger auf Elementtyp kann in einen Wert vom Typ bool konvertiert werden. Ein Nullwert, ein Nullzeigerwert oder ein Nullelementzeigerwert wird in false konvertiert. Jeder andere Wert wird in true konvertiert. Für die Direktinitialisierung (8.5) kann ein Wert vom Typ std :: nullptr_t in einen Wert vom Typ bool konvertiert werden. Der resultierende Wert ist falsch.

Die einzige Stelle in diesem Dokumententwurf, an der die Konvertierung von std::nullptr_teinem integralen Typ erwähnt wird, befindet sich im Abschnitt "reinterpret_cast":

5.2.10 Cast neu interpretieren
...
(4) Ein Zeiger kann explizit in einen beliebigen Integraltyp konvertiert werden, der groß genug ist, um ihn zu halten. Die Zuordnungsfunktion ist implementierungsdefiniert. [Hinweis: Es ist nicht überraschend für diejenigen, die die Adressierungsstruktur des zugrunde liegenden Computers kennen. - Endnote] Ein Wert vom Typ std :: nullptr_t kann in einen ganzzahligen Typ konvertiert werden. Die Konvertierung hat dieselbe Bedeutung und Gültigkeit wie eine Konvertierung von (void *) 0 in den Integraltyp. [Hinweis: Ein reinterpret_cast kann nicht verwendet werden, um einen Wert eines beliebigen Typs in den Typ std :: nullptr_t zu konvertieren. - Endnote]

Aus diesen beiden Beobachtungen könnte man (IMHO) vernünftigerweise vermuten, dass der MSVCCompiler korrekt ist.

EDIT : Ihre Verwendung der "funktionalen Notation Cast" kann jedoch tatsächlich das Gegenteil suggerieren! Der MSVCCompiler hat kein Problem mit der Verwendung eines C-Stils, zum Beispiel:

uintptr_t answer = (uintptr_t)(nullptr);

aber (wie in Ihrem Code) beschwert es sich darüber:

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

Aus demselben Standardentwurf:

5.2.3 Explizite Typkonvertierung (funktionale Notation)
(1) Ein einfacher Typspezifizierer (7.1.6.2) oder Typennamenspezifizierer (14.6) gefolgt von einer in Klammern gesetzten Ausdrucksliste erstellt einen Wert des angegebenen Typs in der Ausdrucksliste. Wenn die Ausdrucksliste ein einzelner Ausdruck ist, entspricht der Typkonvertierungsausdruck (in seiner Definiertheit und, falls in seiner Bedeutung definiert) dem entsprechenden Besetzungsausdruck (5.4). ...

Der "entsprechende Besetzungsausdruck (5.4)" kann sich auf eine Besetzung im C-Stil beziehen.

Adrian Mole
quelle
0

Alle sind standardkonform (siehe Entwurf n4659 für C ++).

nullptr ist in [lex.nullptr] definiert als:

Das Zeigerliteral ist das Schlüsselwort nullptr. Es ist ein Wert vom Typ std :: nullptr_t. [Hinweis: ..., ein Wert dieses Typs ist eine Nullzeigerkonstante und kann in einen Nullzeigerwert oder einen Nullelementzeigerwert konvertiert werden.]

Auch wenn Noten nicht normativ sind, macht dies eine deutlich , dass für den Standard, nullptrerwartet wird , auf einen Null umgewandelt werden Zeigerwert.

Wir finden später in [conv.ptr]:

Eine Nullzeigerkonstante ist ein ganzzahliges Literal mit dem Wert Null oder ein Wert vom Typ std :: nullptr_t. Eine Nullzeigerkonstante kann in einen Zeigertyp konvertiert werden. .... Eine Nullzeigerkonstante vom Integraltyp kann in einen Wert vom Typ std :: nullptr_t konvertiert werden.

Auch hier verlangt der Standard, dass er 0in a std::nullptr_tund nullptrin einen beliebigen Zeigertyp konvertiert werden kann.

Ich lese, dass der Standard keine Anforderung hat, ob nullptrer direkt in einen integralen Typ konvertiert werden kann oder nicht. Von diesem Punkt aus:

  • MSVC hat eine strenge Lesart und verbietet die Konvertierung
  • Clang und gcc verhalten sich so, als wäre eine Zwischenkonvertierung void *beteiligt.
Serge Ballesta
quelle
1
Ich denke das ist falsch. Einige Nullzeigerkonstanten sind ganzzahlige Literale mit dem Wert Null, jedoch nullptrnicht, weil sie den nicht integralen Typ haben std::nullptr_t. 0 kann in einen std::nullptr_tWert konvertiert werden, jedoch nicht in das Literal nullptr. Dies ist alles beabsichtigt, std::nullptr_tist ein eingeschränkterer Typ, um unbeabsichtigte Konvertierungen zu verhindern.
MSalters
@ MSalters: Ich denke, dass Sie Recht haben. Ich wollte es umformulieren und habe es falsch gemacht. Ich habe meinen Beitrag mit Ihrem Kommentar bearbeitet. Danke für deine Hilfe.
Serge Ballesta