On 09/05/2026 14:21, Jonathan Wakely wrote:
On Sat, 9 May 2026, 09:40 Christopher Bazley via Gcc, <[email protected]
<mailto:[email protected]>> wrote:
This flaw in the design of C++ is also what necessitated the invention
of nullptr, which would otherwise be unnecessary. Had the purpose of
'void *' not been misunderstood, nullptr could simply have been defined
as a macro: ((void *)0).
No it couldn't.
Yes, my words misrepresented the order of events.
It is surprising to me that nullptr wasn't invented until C++11, given
that there were known issues with overloading since the inception of C
with classes. Those early problems with overloading do appear to stem
from Stroustrup's redefinition of the semantics of the 'void *' type. I
stand by that.
The idea of using '0' as a null pointer constant seems so bad to me that
I can scarcely believe it was standard C++ for so long. That is what led
to my wrong statement.
This thread is getting more and more of topic, but defining nullptr as
just a void* would not allow the useful property of overloading
functions like unique_ptr::operator= on whether the argument is an
actual pointer (which would replace the owned pointer, but is disallowed
by plain assignment) or is the special nullptr value (which is allowed).
That is:
uptr = (void*)0; // error
uptr = nullptr; // ok
Thanks for the explanation.
I can see that nullptr has some use for template type deduction,
although based on your description I find that usage questionable.
Assignments do not generally have different semantics depending on
whether the assigned expression is a literal or not.
I think you have an overly simplistic idea of why nullptr was added to
C++, leading to an incorrect conclusion about it only being necessary
because of C++ pointer conversion rules.
My "overly simplistic idea" was derived from my reading of Stroustrup
(1994, reprinted 1998), 'The Design and Evolution of C++' (pp 230):
================
A void* cannot be assigned to anything without a cast. Allowing
implicit conversions of void* to other pointer types would open a
serious hole in the type sustem. One might make a special case for
(void*)0, but special cases should only be admitted in dire need. Also,
C++ usage was determined long before there was an ANSI C standard, and I
do not want to have any critical part of C++ rely on a macro.
Consequently, I used plain 0, and that has worked very well over the
years. People who insist on a symbolic constant usually defined one of
const int NULL = 0; // or
#define NULL 0
As far as the compiler is concerned, NULL and 0 are then synonymous.
Unfortunately, so many people have added definitions NULL, NIL, Null,
null, etc. to their code that providing yet another definition can be
hazardous.
There is one kind of mistake that is not caught when 0 (however spelled)
is used for the null pointer. Consider:
void f(char*);
void g() { f(0); } // calls f(char*)
Now add another f() and the meaning of g() silently changes:
void f(char*);
void f(int);
void g() { f(0); } // calls f(int)
This is an unfortunately side effect of 0 being an int that can be
promoted to the null pointer, rather than a direct specification of the
null pointer. I think a good compiler should warn, but I didn't think
of that in time for CFront. Making the call to f(0) ambiguous rather
than resolving it in favour of f(int) would be feasible, but would
probably not satisfy the people would want NULL or nil to be magical.
================
What I remembered was Stroustrup's explanation (which I don't find
particularly compelling) of why C++ did not use (void*)0 as a null
pointer constant. However, I forgot that his book can't be used directly
to justify the invention of nullptr, because nullptr hadn't been
invented when he wrote the book.
It exists for reasons related to template type deduction and
overloading, not because void* isn't convertible to other pointer types.
I don't think nullptr is necessary to resolve the problem with
overloading that Stroustrup mentioned in his book. Consider his example,
assuming that NULL is defined as ((void *)0) and assuming that C++ had
not departed from C's semantics for void *:
void f(char*);
void g() { f(NULL); } // calls f(char*)
But also:
void f(char*);
void f(int);
void g() { f(NULL); } // still calls f(char*)
--
Christopher Bazley
Staff Software Engineer, GNU Tools Team.
Arm Ltd, 110 Fulbourn Road, Cambridge, CB1 9NJ, UK.
http://www.arm.com/