A bit of prehistory.
I've been writing a game engine for quite some time. It's divided into several static libraries, like "utils", "rsbin" (resource system), "window", which are then linked into a single executable.
It is a crossplatform engine, being compiled for Windows and for Android. Under Windows, I compile it with MinGW. Under Android, with CCTools, which is an interface to native gcc.
One of the base classes is utils::RefObject
, which represents a concept similar to Windows's IUnknown: it provides a reference counter to determine its lifetime and a method for querying a specific interface from base class pointer. There's also template< typename T > utils::Ref
, designed specifically for this kind of objects. It holds an std::atomic< utils::RefObject* >
and automatically updates its object's refcount upon construction, assignment and destruction, analogously to std::shared_ptr
. It also allows to implicitly convert RefObjects of different types through their querying methods. Though, it's inefficient to query an object for its own type, so, utils::Ref
overloads most of its operators, e. g. there's specific utils::Ref< T >::Ref( T* ptr )
constructor, which simply increments the passed object's refcount, and general utils::Ref< T >::Ref( RefObject* ptr )
, which queries its argument for instance of T and throws an exception on failure (don't worry, though, there's of course a method for the soft cast).
But having just these two methods introduces a problem: you cannot explicitly initialize utils::Ref
with a null pointer, as it is ambiguous; so there's also utils::Ref< T >::Ref( nullptr_t )
to provide a way to do it.
Now, we are getting to the problem at hand. In the header file, the prototype is spelled exactly as above, without any preceding std::
. Note that I don't use using namespace
either. For a long time, this worked.
Now, I'm working on a graphics system. It existed before, but it was rather rudimentary, so I didn't even noticed that <gl.h> actually defines only OpenGL 1.1, while for newer versions you should go through <glext.h>. Now, it became necessary to use the latter. But including it broke the old reference class.
Judging from error messages, MinGW now has problems with that nullptr_t
in prototypes. I've done a quick search on the Web and found that often it's referred to as std::nullptr_t
. Though, not everywhere.
Quick sumup: I had nullptr_t
without either std::
or using namespace
compiling fine until I included <glext.h> before the header.
The site I've been using so far, cplusplus.com/reference, suggests that global ::nullptr_t
is exactly how it should be. On the other hand, en.cppreference.com wiki tells that it's actually std::nullptr_t
.
A quick test program, a helloworld with void foo( int )
and void foo( nullptr_t )
, failed to compile and the reason now is explicit "error: 'nullptr_t' was not declared in this scope"
with suggestion to use std::nullptr_t
instead.
It won't be hard to add std::
where needed; but this case left me rather curious.
cplusplus.com was actually lying? => Answered in commets, yes. It's an inaccurate source.
Then, if nullptr_t
actually resides in namespace std
, why did utils::Ref
compile? => With suggestions in comments, ran a couple of test and discovered that <mutex>, included in some other header, when placed before any stddef header, defines global ::nullptr_t
. Certainly not an ideal behavior, but it's not a major bug. Probably should report it to MinGW/GCC developers anyway.
Why inclusion of <glext.h> breaks it? => When any stddef header is included before <mutex>, the type is defined according to the standard, as std::nullptr_t
. <glext.h> includes <windows.h>, which, in turn, certainly includes the stddef header, among with a whole pack of others which are needed for WinAPI.
Here are the sources, defining the class in question:
(the latter 2 are included and so may affect too)
As suggested in the comments, I ran g++ -E on a test case which compiled, and found a quite interesting bit in <stddef.h>:
#if defined(__cplusplus) && __cplusplus >= 201103L
#ifndef _GXX_NULLPTR_T
#define _GXX_NULLPTR_T
typedef decltype(nullptr) nullptr_t;
#endif
#endif /* C++11. */
Now to find where _GXX_NULLPTR_T
is defined else... a quick GREP through MinGW's files didn't find anything besides this stddef.h
So, it's still a mystery why and how it's getting disabled. Especially when including just <stddef.h> and nothing else does not define nullptr_t
anywhere, despite the bit above.
The type of nullptr
is defined in namespace ::std
, so the correct qualification is ::std::nullptr_t
. Of course, this means you normally spell it std::nullptr_t
in practice.
Quoting C++11:
2.14.7/1:
The pointer literal is the keyword
nullptr
. It is a prvalue of typestd::nullptr_t
.
18.2/9:
nullptr_t
is defined as follows:namespace std { typedef decltype(nullptr) nullptr_t; }
The type for which
nullptr_t
is a synonym has the characteristics described in 3.9.1 and 4.10. [ Note: Althoughnullptr
’s address cannot be taken, the address of anothernullptr_t
object that is an lvalue can be taken. —end note ]
<stddef.h>
also enters into the picture. 18.2 talks about <cstddef>
, so that's the C++ header where std::nullptr_t
is defined. Per D.5/2:
Every C header, each of which has a name of the form
name.h
, behaves as if each name placed in the standard library namespace by the correspondingcname
header is placed within the global namespace scope.
Which means that including <stddef.h>
gives you access to ::nullptr_t
. But since that's supposed to be a C header, I would advise against relying on this in C++ code (even if it's formally valid).
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