I am curious to know how nullptr
works. Standards N4659 and N4849 say:
- it has to have type
std::nullptr_t
; - you cannot take its address;
- it can be directly converted to a pointer and pointer to member;
sizeof(std::nullptr_t) == sizeof(void*)
;- its conversion to
bool
isfalse
; - its value can be converted to integral type identically to
(void*)0
, but not backwards;
So it is basically a constant with the same meaning as (void*)0
, but it has a different type. I have found the implementation of std::nullptr_t
on my device and it is as follows.
#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
I am more interested in the first part though. It seems to satisfy the points 1-5, but I have no idea why it has a subclass __nat and everything related to it. I would also like to know why it fails on integral conversions.
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){};
};
nullptr_t
is a fundamental type. How isint
implemented? – Charleen#ifdef _LIBCPP_HAS_NO_NULLPTR
. This seems like a best-effort workaround for when the compiler doesn't providenullptr
. – Canterfloat
is implemented – Keelinnullptr_t
is a fundamental type. Implementing it as a class type does not make a conforming implementation. See chris's comment. – Charleen__nat
does and why it is there, maybe this will help me in my own code. – Germistonoperator bool
will allow it to be converted to, say,int
or class types constructible frombool
. – Charleenis_class
andis_null_pointer
cannot both be true for the same type. Only one of the primary type category functions can return true for a specific type. – Toffeyis_class
andis_null_pointer
could lie, making an explicit exception for thisstd::nullptr_t
type by returning "incorrect" values. – Toffeynullptr
keyword is very similar to thetrue
andfalse
keywords: all are literals which represent values, for a type which has very few values in the first place. There are only twobool
values, and only onestd::nullptr_t
value. – Gilligan