Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Where does nullptr_t reside?

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:

  • utils/ref.hpp
  • utils/ref.cpp
  • utils/refobject.hpp
  • utils/refobject.cpp
  • utils/logger.hpp => This one uses a mutex to avoid line tearing during output.
  • utils/cbase.hpp

(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.

like image 654
Delfigamer Avatar asked Feb 24 '15 13:02

Delfigamer


1 Answers

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 type std::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: Although nullptr’s address cannot be taken, the address of another nullptr_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 corresponding cname 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).

like image 188
Angew is no longer proud of SO Avatar answered Sep 21 '22 16:09

Angew is no longer proud of SO