Jestem ciekawy, jak nullptr
działa. Normy N4659 i N4849 mówią:
- musi mieć typ
std::nullptr_t
; - nie możesz wziąć jego adresu;
- można go bezpośrednio przekonwertować na wskaźnik, a wskaźnik na element członkowski;
sizeof(std::nullptr_t) == sizeof(void*)
;- jego konwersja na
bool
jestfalse
; - jego wartość można przekonwertować na typ całkowy identycznie
(void*)0
, ale nie wstecz;
Jest to w zasadzie stała o takim samym znaczeniu jak (void*)0
, ale ma inny typ. Znalazłem implementację std::nullptr_t
na moim urządzeniu i wygląda ona następująco.
#ifdef _LIBCPP_HAS_NO_NULLPTR
_LIBCPP_BEGIN_NAMESPACE_STD
struct _LIBCPP_TEMPLATE_VIS nullptr_t
{
void* __lx;
struct __nat {int __for_bool_;};
_LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR nullptr_t() : __lx(0) {}
_LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR nullptr_t(int __nat::*) : __lx(0) {}
_LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR operator int __nat::*() const {return 0;}
template <class _Tp>
_LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR
operator _Tp* () const {return 0;}
template <class _Tp, class _Up>
_LIBCPP_INLINE_VISIBILITY
operator _Tp _Up::* () const {return 0;}
friend _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR bool operator==(nullptr_t, nullptr_t) {return true;}
friend _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR bool operator!=(nullptr_t, nullptr_t) {return false;}
};
inline _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR nullptr_t __get_nullptr_t() {return nullptr_t(0);}
#define nullptr _VSTD::__get_nullptr_t()
_LIBCPP_END_NAMESPACE_STD
#else // _LIBCPP_HAS_NO_NULLPTR
namespace std
{
typedef decltype(nullptr) nullptr_t;
}
#endif // _LIBCPP_HAS_NO_NULLPTR
Bardziej interesuje mnie jednak pierwsza część. Wydaje się, że spełnia kryteria 1-5, ale nie mam pojęcia, dlaczego ma podklasę __nat i wszystko, co się z tym wiąże. Chciałbym również wiedzieć, dlaczego nie udaje się to przy zintegrowanych konwersjach.
struct nullptr_t2{
void* __lx;
struct __nat {int __for_bool_;};
constexpr nullptr_t2() : __lx(0) {}
constexpr nullptr_t2(int __nat::*) : __lx(0) {}
constexpr operator int __nat::*() const {return 0;}
template <class _Tp>
constexpr
operator _Tp* () const {return 0;}
template <class _Tp, class _Up>
operator _Tp _Up::* () const {return 0;}
friend constexpr bool operator==(nullptr_t2, nullptr_t2) {return true;}
friend constexpr bool operator!=(nullptr_t2, nullptr_t2) {return false;}
};
inline constexpr nullptr_t2 __get_nullptr_t2() {return nullptr_t2(0);}
#define nullptr2 __get_nullptr_t2()
int main(){
long l = reinterpret_cast<long>(nullptr);
long l2 = reinterpret_cast<long>(nullptr2); // error: invalid type conversion
bool b = nullptr; // warning: implicit conversion
// edditor error: a value of type "std::nullptr_t" cannot be used to initialize an entity of type "bool"
bool b2 = nullptr2;
if (nullptr){}; // warning: implicit conversion
if (nullptr2){};
};
c++
c++17
nullptr
null-pointer
Fullfungo
źródło
źródło
nullptr_t
jest typem podstawowym. Jakint
realizowany?#ifdef _LIBCPP_HAS_NO_NULLPTR
. Wydaje się, że jest to obejście problemu, gdy kompilator nie zapewnianullptr
.nullptr_t
jest to typ podstawowy. Implementacja go jako typu klasy nie stanowi zgodnej implementacji. Zobacz komentarz Chris.is_class
iis_null_pointer
nie mogą być prawdziwe dla tego samego typu. Tylko jedna z funkcji kategorii typu podstawowego może zwrócić wartość true dla określonego typu.Odpowiedzi:
Działa w najprostszy możliwy sposób: przez fiat . Działa, ponieważ standard C ++ mówi, że działa, i działa tak, jak działa, ponieważ standard C ++ mówi, że implementacje muszą działać w ten sposób.
Ważne jest, aby uznać, że niemożliwe jest wdrożenie
std::nullptr_t
przy użyciu reguł języka C ++. Konwersja stałej typu pusty wskaźnikstd::nullptr_t
na wskaźnik nie jest konwersją zdefiniowaną przez użytkownika. Oznacza to, że możesz przejść od stałej zerowej wskaźnika do wskaźnika, a następnie poprzez konwersję zdefiniowaną przez użytkownika do innego typu, wszystko w jednej niejawnej sekwencji konwersji.Nie jest to możliwe, jeśli zaimplementujesz
nullptr_t
jako klasę. Operatory konwersji reprezentują konwersje zdefiniowane przez użytkownika, a niejawne reguły sekwencji konwersji C ++ nie pozwalają na więcej niż jedną konwersję zdefiniowaną przez użytkownika w takiej sekwencji.Więc kod, który opublikowałeś, jest dobrym przybliżeniem
std::nullptr_t
, ale to nic więcej. To nie jest legalna implementacja tego typu. Prawdopodobnie pochodziło to ze starszej wersji kompilatora (pozostawionej w celu zachowania kompatybilności wstecznej), zanim kompilator zapewnił odpowiednią obsługęstd::nullptr_t
. Widać to po tym, że tak#define
jestnullptr
, podczas gdy C ++ 11 mówi, żenullptr
to słowo kluczowe , a nie makro.C ++ nie może zaimplementować
std::nullptr_t
, tak jak C ++ nie może zaimplementowaćint
lubvoid*
. Tylko wdrożenie może zaimplementować te rzeczy. Właśnie dlatego jest to „typ podstawowy”; to część języka .Nie ma niejawnej konwersji ze stałej wskaźnika zerowego na typy całkowe. Istnieje konwersja z
0
na typ integralny, ale to dlatego, że jest to całkowite zero dosłowne, które jest ... liczbą całkowitą.nullptr_t
można rzutować na typ całkowity (viareinterpret_cast
), ale można go domyślnie przekonwertować tylko na wskaźniki i nabool
.źródło
std::nullptr_t
. Tak jak nie możesz napisać typu, który jest dokładnie równoważny wymaganemu zachowaniuint
. Możesz się zbliżyć, ale nadal będą znaczące różnice. I nie mówię o takich wykrywaczach cech,is_class
które ujawniają, że twój typ jest zdefiniowany przez użytkownika. Są pewne rzeczy dotyczące wymaganego zachowania podstawowych typów, których po prostu nie można skopiować za pomocą reguł języka.nullptr_t
”, mówisz zbyt szeroko. A powiedzenie „tylko implementacja może to zaimplementować” tylko myli sprawy. Masz na myśli to, żenullptr_t
nie można go wdrożyć " w bibliotece C ++, ponieważ jest on częścią języka podstawowego.std::nullptr_t
jest wymagane. Podobnie jak C ++, język nie może implementować typu, który robi wszystko, coint
jest wymagane.