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?
Odpowiedzi:
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_t
jest 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śniejreinterpret_cast
sąconst_cast
istatic_cast
(z rozszerzeniami, a także w kombinacji), ale żadna z nich nie może obsadzićstd::nullptr_t
typu integralnego.Ale
reinterpret_cast<my_time_t>(nullptr)
powinno się to udać, ponieważ [expr.reinterpret.cast] / 4 mówi, że wartość typustd::nullptr_t
można przekonwertować na typ całkowy tak, jakbyreinterpret_cast<my_time_t>((void*)0)
to było możliwe, co jest możliwe, ponieważmy_time_t = std::uintptr_t
powinien 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:
źródło
static_cast
w 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 uformowanystatic_cast
niż anreinterpret_cast
), ale żadna nie ma tu zastosowania.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.Chociaż nie mogę znaleźć żadnej wyraźnej wzmianki w tym roboczym projekcie C ++ Standard (od 2014 r.), Że konwersja
std::nullptr_t
na typ integralny jest zabroniona, nie ma również wzmianki, że taka konwersja jest dozwolona!Jednak przypadek konwersji z
std::nullptr_t
nabool
jest wyraźnie wymieniony:Ponadto jedynym miejscem w tym projekcie dokumentu, w którym wymieniono konwersję
std::nullptr_t
na typ integralny, jest sekcja „reinterpret_cast”:Więc z tymi obserwacjami, można by (IMHO) zasadnie przypuszczać, że
MSVC
kompilator jest poprawna.EDYCJA : Jednak użycie „funkcjonalnej obsady notacji” może faktycznie sugerować coś przeciwnego!
MSVC
Kompilator nie ma problemu przy użyciu oddanych C-styl, na przykład:ale (jak w kodzie) narzeka na to:
Jednak z tego samego standardu projektu:
„Odpowiednie wyrażenie rzutowania (5.4)” może odnosić się do rzutowania w stylu C.
źródło
Wszystkie są zgodne ze standardem (ref. Projekt n4659 dla C ++).
nullptr
jest zdefiniowany w [lex.nullptr] jako:Nawet jeśli nuty nie są normatywne, ta wyjaśnia, że dla standardu
nullptr
oczekuje się, że zostaną przekonwertowane na wartość wskaźnika zerowego .Później znajdziemy w [conv.ptr]:
Tutaj znowu tym, co jest wymagane przez standard, jest to, że
0
można je przekonwertować na astd::nullptr_t
i którenullptr
można przekonwertować na dowolny typ wskaźnika.Według mnie norma nie wymaga, aby
nullptr
można ją było bezpośrednio przekształcić w typ integralny. Od tego momentu:void *
była zaangażowana konwersja pośrednia .źródło
nullptr
nie dlatego, że ma typ niecałkowitystd::nullptr_t
. 0 można przekonwertować nastd::nullptr_t
wartość, ale nie na literałnullptr
. Wszystko to jest celowe,std::nullptr_t
jest bardziej ograniczonym typem, aby zapobiec niezamierzonym konwersjom.