I am curious to know how nullptr
works. Standards N4659 and N4849 say:
std::nullptr_t
;sizeof(std::nullptr_t) == sizeof(void*)
;bool
is false
;(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 is a new keyword introduced in C++11. nullptr is meant as a replacement to NULL . nullptr provides a typesafe pointer value representing an empty (null) pointer. The general rule of thumb that I recommend is that you should start using nullptr whenever you would have used NULL in the past.
The nullptr keyword represents a null pointer value. Use a null pointer value to indicate that an object handle, interior pointer, or native pointer type does not point to an object. Use nullptr with either managed or native code.
C++11 corrects this by introducing a new keyword to serve as a distinguished null pointer constant: nullptr. It is of type nullptr_t, which is implicitly convertible and comparable to any pointer type or pointer-to-member type. It is not implicitly convertible or comparable to integral types, except for bool.
Nullptr vs NULLNULL is 0 (zero) i.e. integer constant zero with C-style typecast to void* , while nullptr is prvalue of type nullptr_t , which is an integer literal that evaluates to zero.
I am curious to know how nullptr works.
It works in the simplest way possible: by fiat. It works because the C++ standard says it works, and it works the way it does because the C++ standard says that implementations must make it work in that fashion.
It's important to recognize that it is impossible to implement std::nullptr_t
using the rules of the C++ language. The conversion from a null pointer constant of type std::nullptr_t
to a pointer is not a user-defined conversion. That means that you can go from a null pointer constant to a pointer, then through a user-defined conversion to some other type, all in a single implicit conversion sequence.
That's not possible if you implement nullptr_t
as a class. Conversion operators represent user-defined conversions, and C++'s implicit conversion sequence rules don't allow for more than one user-defined conversion in such a sequence.
So the code you posted is a nice approximation of std::nullptr_t
, but it is nothing more than that. It is not a legitimate implementation of the type. This was probably from an older version of the compiler (left in for backwards-compatibility reasons) before the compiler provided proper support for std::nullptr_t
. You can see this by the fact that it #define
s nullptr
, while C++11 says that nullptr
is a keyword, not a macro.
C++ cannot implement std::nullptr_t
, just as C++ cannot implement int
or void*
. Only the implementation can implement those things. This is what makes it a "fundamental type"; it's a part of the language.
its value can be converted to integral type identically to (void*)0, but not backwards;
There is no implicit conversion from a null pointer constant to integral types. There is a conversion from 0
to an integral type, but that's because it's the integer literal zero, which is... an integer.
nullptr_t
can be cast to an integer type (via reinterpret_cast
), but it can only be implicitly converted to pointers and to bool
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With