How does c++ nullptr implementation work?
Asked Answered
G

2

21

I am curious to know how nullptr works. Standards N4659 and N4849 say:

  1. it has to have type std::nullptr_t;
  2. you cannot take its address;
  3. it can be directly converted to a pointer and pointer to member;
  4. sizeof(std::nullptr_t) == sizeof(void*);
  5. its conversion to bool is false;
  6. 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){};
};
Germiston answered 18/4, 2020 at 2:36 Comment(16)
nullptr_t is a fundamental type. How is int implemented?Charleen
Note #ifdef _LIBCPP_HAS_NO_NULLPTR. This seems like a best-effort workaround for when the compiler doesn't provide nullptr.Canter
@L.F. Similar to how float is implementedKeelin
nullptr is not guaranteed to be implemented, it can have a special definition like the one in the code.Germiston
@Germiston The standard says that nullptr_t is a fundamental type. Implementing it as a class type does not make a conforming implementation. See chris's comment.Charleen
Related: safe bool idiom Is the safe-bool idiom obsolete in C++11?Charleen
@L. F. Ok, makes sense. I still want to know what __nat does and why it is there, maybe this will help me in my own code.Germiston
@L.F. Does the standard technically require that a fundamental type is not a class type?Nightfall
@Germiston it's basically used for the pre-C++11 safe bool idiom. A simple operator bool will allow it to be converted to, say, int or class types constructible from bool.Charleen
@Nightfall Wow, I always assumed that to be true before, but seems the closest I can find is a note that says "There are two kinds of types: fundamental types and compound types" ...Charleen
@eerorika: is_class and is_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.Toffey
@NicolBolas In simple words: Primitive types and User-defined types are mutually exclusive classificationsKeelin
@NicolBolas, Could the implementation technically special-case it so those still return correct results?Canter
@chris: It's version of is_class and is_null_pointer could lie, making an explicit exception for this std::nullptr_t type by returning "incorrect" values.Toffey
If it helps, the nullptr keyword is very similar to the true and false keywords: all are literals which represent values, for a type which has very few values in the first place. There are only two bool values, and only one std::nullptr_t value.Gilligan
@Canter The implementation can do any kinds of "cheats" it likes to get behavior of conforming programs to act like the imaginary C++ "virtual machine", per the "as if" rule (timsong-cpp.github.io/cppwp/intro.abstract#1 and footnote).Gilligan
T
24

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 #defines 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.

Toffey answered 18/4, 2020 at 3:41 Comment(7)
What is meant by "it is impossible to implement std::nullptr_t using the rules of the C++ language"? Does it mean a C++ compiler cannot be completely written in C++ itself (I'm guessing not)?Stamm
@northerner: I mean that you cannot write a type that is exactly equivalent to the behavior required of std::nullptr_t. Just as you cannot write a type that is exactly equivalent to the behavior required of int. You can get close, but there will still be significant differences. And I'm not talking about trait detectors like is_class that expose that your type is user-defined. There are things about the required behavior of fundamental types that you simply cannot copy by using the rules of the language.Toffey
Just a wording quibble. When you say "C++ cannot implement nullptr_t" you speak too broadly. And saying "only the implementation can implement it" only confuses matters. What you mean is that nullptr_t cannot be implemented" in the C++ library because it's part of the base language.Rikkiriksdag
@Spencer: No, I meant exactly what I said: C++ the language cannot be used to implement a type that does everything that std::nullptr_t is required to do. Just as C++ the language cannot implement a type that does everything that int is required to do.Toffey
You did not say "C++ cannot be used to implement", you said "C++ cannot implement". If you edit your answer to say the former rather than the latter, I will be OK with that.Rikkiriksdag
@Spencer: From the first sentence, second paragraph: "it is impossible to implement std::nullptr_t using the rules of the C++ language." Context matters.Toffey
And I'm not contradicting that. I just want you to word it better in your answer, because words matter.Rikkiriksdag
A
1

nullptr is a fundamental type, it's part of the C++ language implementation. Unlike to NULL keyword which is a macro evaluated in integer constant 0, the nullptr is actually a keyword implying pointer literal. As int is actually a builtin type in the language of size 4 byte (x64), nullptr is also a builtin type of size 8 byte (x64)

Alberto answered 12/11, 2021 at 6:42 Comment(1)
so you mean nullptr_t is type of a builtin type?Spirit

© 2022 - 2025 — McMap. All rights reserved.