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?
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.
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.
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 anint
) 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 functionsf(int)
andf(char*)
, the callf(0)
unambiguously resolves tof(int)
. There is no way to write a call tof(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 macroNULL
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 integer0
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.