Czy nullptr można przekonwertować na uintptr_t? Różne kompilatory się nie zgadzają

10

Rozważ ten program:

#include <cstdint>
using my_time_t = uintptr_t;

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

Nie udało się skompilować z 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

ale clang (9.0.1) i gcc (9.2.1) „jedzą” ten kod bez żadnych błędów.

Podoba mi się zachowanie MSVC, ale czy jest to potwierdzone przez standard? Innymi słowy, czy jest to błąd w clang / gcc, czy można interpretować standard, że jest to właściwe zachowanie z gcc / clang?

użytkownik1244932
źródło
2
Czytam to jako inicjalizację kopii z rzutowania w stylu funkcji. To jest następnie interpretowane przez kompilator jako jeden z rzutowań C ++ „nawet jeśli nie można go skompilować”. Być może istnieje niezgodność między kompilatorami co do sposobu interpretacji obsady
wreckgar23
O ile mi wiadomo, MSVC v19.24 nie obsługuje trybu C ++ 11. Czy zamiast tego chodziło Ci o C ++ 14 lub C ++ 17?
orzech

Odpowiedzi:

5

Moim zdaniem MSVC nie zachowuje się zgodnie ze standardami.

Opieram tę odpowiedź na C ++ 17 (projekt N4659), ale C ++ 14 i C ++ 11 mają równoważne brzmienie.

my_time_t(nullptr)jest wyrażeniem postfiksowym, a ponieważ my_time_tjest typem i (nullptr)jest pojedynczym wyrażeniem na liście inicjalizującej w nawiasach, jest dokładnie równoważne jawnemu wyrażeniu rzutowanemu. ( [expr.type.conv] / 2 )

Jawna obsada próbuje kilku różnych specyficznych rzutowań w C ++ (z rozszerzeniami), w szczególności także reinterpret_cast. ( [expr.cast] /4.4 ) Obsady wypróbowane wcześniej reinterpret_castconst_casti static_cast(z rozszerzeniami, a także w kombinacji), ale żadna z nich nie może obsadzić std::nullptr_ttypu integralnego.

Ale reinterpret_cast<my_time_t>(nullptr)powinno się to udać, ponieważ [expr.reinterpret.cast] / 4 mówi, że wartość typu std::nullptr_tmożna przekonwertować na typ całkowy tak, jakby reinterpret_cast<my_time_t>((void*)0)to było możliwe, co jest możliwe, ponieważ my_time_t = std::uintptr_tpowinien być typem wystarczająco dużym, aby reprezentował wszystkie wartości wskaźnika, a pod tym warunkiem ten sam standardowy akapit pozwala na konwersję void*na typ integralny.

Szczególnie dziwne jest to, że MSVC zezwala na konwersję, jeśli używana jest notacja rzutowana zamiast notacji funkcjonalnej:

const my_time_t t = (my_time_t)nullptr;
orzech włoski
źródło
1
Tak. Zauważ, że static_castw szczególności niektóre przypadki mają na celu uwięzienie drabiny odlewanej w stylu C (np. Rzut odlewany w stylu C na niejednoznaczną podstawę jest raczej źle uformowany static_castniż an reinterpret_cast), ale żadna nie ma tu zastosowania.
TC
my_time_t(nullptr)jest z definicji taki sam (my_time_t)nullptr, więc MSVC z pewnością błędnie przyjmuje jedno i odrzuca drugie.
Richard Smith
2

Chociaż nie mogę znaleźć żadnej wyraźnej wzmianki w tym roboczym projekcie C ++ Standard (od 2014 r.), Że konwersja std::nullptr_tna typ integralny jest zabroniona, nie ma również wzmianki, że taka konwersja jest dozwolona!

Jednak przypadek konwersji z std::nullptr_tna bool jest wyraźnie wymieniony:

4.12 Konwersje boolowskie
Wartość arytmetyczna, nieskalowane wyliczenie, wskaźnik lub wskaźnik na typ elementu można przekształcić na wartość typu bool. Wartość zerowa, wartość wskaźnika zerowego lub wartość wskaźnika pustego elementu są konwertowane na false; każda inna wartość jest konwertowana na true. W przypadku bezpośredniej inicjalizacji (8.5) wartość parametru std :: nullptr_t można przekonwertować na wartość typu bool; wynikowa wartość to false.

Ponadto jedynym miejscem w tym projekcie dokumentu, w którym wymieniono konwersję std::nullptr_tna typ integralny, jest sekcja „reinterpret_cast”:

5.2.10 Reinterpretacja obsady
...
(4) Wskaźnik można jawnie przekonwertować na dowolny typ integralny wystarczająco duży, aby go pomieścić. Funkcja mapowania jest zdefiniowana w implementacji. [Uwaga: Nie ma w tym nic zaskakującego dla tych, którzy znają strukturę adresowania komputera bazowego. - uwaga końcowa] Wartość typu std :: nullptr_t można przekonwertować na typ całkowy; konwersja ma takie samo znaczenie i ważność jak konwersja (void *) 0 na typ całkowy. [Uwaga: Nie można użyć reinterpret_cast do konwersji wartości dowolnego typu na typ std :: nullptr_t. - uwaga końcowa]

Więc z tymi obserwacjami, można by (IMHO) zasadnie przypuszczać, że MSVCkompilator jest poprawna.

EDYCJA : Jednak użycie „funkcjonalnej obsady notacji” może faktycznie sugerować coś przeciwnego! MSVCKompilator nie ma problemu przy użyciu oddanych C-styl, na przykład:

uintptr_t answer = (uintptr_t)(nullptr);

ale (jak w kodzie) narzeka na to:

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

Jednak z tego samego standardu projektu:

5.2.3 Jawna konwersja typu (notacja funkcjonalna)
(1) Specyfikator typu prostego (7.1.6.2) lub specyfikator nazwy typu (14.6), po którym następuje nawiasowana lista wyrażeń konstruuje wartość określonego typu na podstawie listy wyrażeń. Jeśli lista wyrażeń jest pojedynczym wyrażeniem, wyrażenie konwersji typu jest równoważne (w zdefiniowaniu i jeśli zdefiniowano w znaczeniu) z odpowiednim wyrażeniem rzutowanym (5.4). ...

„Odpowiednie wyrażenie rzutowania (5.4)” może odnosić się do rzutowania w stylu C.

Adrian Mole
źródło
0

Wszystkie są zgodne ze standardem (ref. Projekt n4659 dla C ++).

nullptr jest zdefiniowany w [lex.nullptr] jako:

Dosłowny wskaźnik to słowo kluczowe nullptr. Jest to wartość typu std :: nullptr_t. [Uwaga: ..., wartość tego typu jest stałą wskaźnika zerowego i można ją przekonwertować na wartość wskaźnika zerowego lub wartość wskaźnika pustego elementu.]

Nawet jeśli nuty nie są normatywne, ta wyjaśnia, że ​​dla standardu nullptroczekuje się, że zostaną przekonwertowane na wartość wskaźnika zerowego .

Później znajdziemy w [conv.ptr]:

Stała wskaźnika zerowego jest literałem całkowitym o wartości zero lub wartością typu std :: nullptr_t. Stałą wskaźnika zerowego można przekonwertować na typ wskaźnika; .... Stałą wskaźnika zerowego typu całkowego można przekonwertować na wartość typu std :: nullptr_t.

Tutaj znowu tym, co jest wymagane przez standard, jest to, że 0można je przekonwertować na a std::nullptr_ti które nullptrmożna przekonwertować na dowolny typ wskaźnika.

Według mnie norma nie wymaga, aby nullptrmożna ją było bezpośrednio przekształcić w typ integralny. Od tego momentu:

  • MSVC ma ścisłą lekturę i zabrania konwersji
  • Clang i gcc zachowują się tak, jakby void *była zaangażowana konwersja pośrednia .
Serge Ballesta
źródło
1
Myślę, że to źle. Niektóre stałe wskaźnika zerowego są literałami całkowitymi o wartości zero, ale nullptrnie dlatego, że ma typ niecałkowity std::nullptr_t. 0 można przekonwertować na std::nullptr_twartość, ale nie na literał nullptr. Wszystko to jest celowe, std::nullptr_tjest bardziej ograniczonym typem, aby zapobiec niezamierzonym konwersjom.
MSalters
@MSalters: Myślę, że masz rację. Chciałem to przeredagować i zrobiłem to źle. Zredagowałem mój post z Twoim komentarzem. Dziękuję za pomoc
Serge Ballesta,