Search code examples
c++pointersc++11metaprogramminggeneric-programming

matching nullptr by T *


Using boost::variant of pointers to AST nodes, which can contain value of special type std::nullptr_t, indicating emptiness, I faced the problem: generic visitors of the form [] (auto /* const */ * p) { /* use p */; } or of the form:

struct V
{
    template< typename T >
    void operator () (T /* const */ * p)
    { /* use p */; }
};

can't deal with value of std::nullptr_t type.

There is a plenty of workarounds one can imagine, but the question rising: is there good explanation why there no (very likely highly restricted) decltype(*nullptr) type in the language (*nullptr is ill-formed and std::remove_pointer_t< std::nullptr_t > is std::nullptr_t in libc++)? Are there theoretical reasons for this?


Solution

  • is there good explanation why there no (very likely highly restricted) decltype(*nullptr) type in the language (*nullptr is ill-formed and std::remove_pointer_t< std::nullptr_t > is std::nullptr_t in libc++)? Are there theoretical reasons for this?

    I think to answer this question, we must look at N1601 proposed by Herb Sutter and Bjarne Stroustrup.

    A couple sections stand out to me, particularly

    4.10 [conv.ptr]

    A null pointer constant or an object of type nullptr_t can be converted to a pointer type; the result is the null pointer value of that type

    and 4.11 [conv.mem]:

    A null pointer constant (4.10) or an object of type nullptr_t (4.10) can be converted to a pointer to member type; the result is the null member pointer value of that type

    So if the result of passing a nullptr or nullptr_t is a null pointer of the given pointer type, then it makes sense that dereferencing it (e.g., via decltype(*nullptr) would be the same as dereferencing any other kind of null pointer. (in the specific case of delctype(*nullptr) I believe it's akin to dereferencing a null void*). That is, you shouldn't do it.

    std::remove_pointer_t< std::nullptr_t > is std::nullptr_t

    The reason this is true is easy enough, but the WHY is harder to get at.

    The reason:

    std::nullptr_t is the type of the null pointer literal, nullptr. It is a distinct type that is not itself a pointer type or a pointer to member type.

    Given that, it makes sense that std::remove_pointer_t would have no effect because nullptr_t is not a pointer type.

    The why

    In N1601, Sutter and Stroustrup said

    nullptr_t is not a reserved word. It is a typedef (as its _t typedef indicates) for decltype(nullptr) defined in <cstddef>. We do not expect to see much direct use of nullptr_t in real programs.

    Indeed this is what actually seems to have happened. Clang 3.9.0, for example has the following in stddef.h:

    namespace std { typedef decltype(nullptr) nullptr_t; }
    using ::std::nullptr_t;
    

    (and also they were right about not much nullptr_t appearing in many programs).

    This still doesn't explain WHY it is defined as such. To do that, I think we need to go back in time a little farther to N1488, also by Sutter and Stroustrup where they said:

    This use of the value 0 to mean different things (a pointer constant and an int) in C++ has caused problems since at least 1985 in teaching, learning, and using C++. In particular:

    • Distinguishing between null and zero. The null pointer and an integer 0 cannot be distinguished well for overload resolution. For example, given two overloaded functions f(int) and f(char*), the call f(0) unambiguously resolves to f(int). There is no way to write a call to f(char*) with a null pointer value without writing an explicit cast (i.e., f((char*)0)) or using a named variable. Note that this implies that today’s null pointer, 0, has no utterable type.

    • Naming null. Further, programmers have often requested that the null pointer constant have a name (rather than just 0). This is one reason why the macro NULL exists, although that macro is insufficient. (If the null pointer constant had a type-safe name, this would also solve the previous problem as it could be distinguished from the integer 0 for overload resolution and some error detection.)

    Which I think explains the why pretty well; programmers needed a way to distinguish between pointers and integer values for in overloads, and since NULL is generally defined as 0, which is usually interpreted as an integer type, there was no easy way to force overload resolution to select the pointer overload. Now that we have nullptr, we can distinguish between pointer and non-pointer types which avoids this issue altogether.