6

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.

Delfigamer
  • 317
  • 1
  • 8
  • look at the preprocessed output to see where `::nullptr_t` is introduced. – tenfour Feb 24 '15 at 13:09
  • 3
    FWIW, cplusplus.com is known for being inaccurate. – Angew is no longer proud of SO Feb 24 '15 at 13:14
  • It belongs in std, non-zero odds that this source code saw another compiler first that had `using ::std::nullptr_t;` in a header. Like MSVC++. – Hans Passant Feb 24 '15 at 13:20
  • @HansPassant Never used another compiler on Windows for this project. – Delfigamer Feb 24 '15 at 13:26
  • @tenfour Oh, that helped! Edited OP. – Delfigamer Feb 24 '15 at 13:31
  • @Delfigamer It doesn't look like you actually include `` anywhere, and are relying on some other header including it on your behalf. That's unreliable and won't always work. If you want something defined in that header, include that header. –  Feb 24 '15 at 13:45
  • 1
    Not to be rude, but is 90% of the question necessary? –  Feb 24 '15 at 13:56
  • @remyabel It answers to possible questions like "why"... "why"... and "why"... No, you can skip it right to the bold line. – Delfigamer Feb 24 '15 at 14:01
  • +1 for spending so much effort to add context to the question. It is always a tough one to decide how much context to provide. – BitTickler Feb 24 '15 at 14:53
  • Stupid question on my behalf: Why is he using nullptr_t and not nullptr directly? Is doing so bad for any reason? In other terms - why would one ever need the type of a nullptr? Would not void* be the type used in those cases? – BitTickler Feb 24 '15 at 14:56
  • @user2225104 As stated in OP, `nullptr_t` is used for overloading functions, accepting different kinds of pointers, as otherwise calling them with null pointer would require its explicit typecasting to one of accepted types. When you have `void foo( int* )` and `void foo( double* )`, call to `foo( 0 )` is ambiguous: the compiler doesn't know which function to call. We can typecast the zero to tell which foo we need: `foo( ( double* )0 )` will be resolved to the latter one. But C++11 gives us a more elegant way to do this, by introducing a special-case `void foo( nullptr_t )`... – Delfigamer Feb 24 '15 at 15:05
  • @user2225104 ...and then calling `foo( nullptr )` which will unambiguously resolve to its own function. In `utils::Ref`, described in OP, this syntax is used to explicitly initialize the reference with null pointer and to assign one when we don't longer need an object it was holding and giving a new one does not make sense. – Delfigamer Feb 24 '15 at 15:08
  • @user2225104 [Also, there's this](http://stackoverflow.com/questions/13816385/what-are-the-advantages-of-using-nullptr/13816448) ;) – Delfigamer Feb 24 '15 at 15:12
  • I start to understand what is going on. Basically, I wonder why they keep splitting functionality between language and std libraries. If nullptr is an intrinsic "value", so should be std::nullptr_t be nullptr_t and language defined (no headers). Same mess they produced with lambda functions and std::function. Just imagine if true and false were language values and bool would not exist. Anyone would see how sick that would be. – BitTickler Feb 24 '15 at 15:45
  • @user2225104 I had this thought about `nullptr_t` too, as I don't see how it is different to `bool` enough so that `bool`, `false` and `true` are all intrinsics, while the type of intrinsic `nullptr` is some declaration in a C header file. It would be more logical to either introduce a written class `std::nullptr_t` with `nullptr_t const nullptr` (which is totally possible in C++) or make them both keywords. Regarding closures and, similarly, initialization lists, they are concepts complex enough to be irrepresentable with elementary types, so there's not many other ways to implement them. – Delfigamer Feb 24 '15 at 18:42
  • 1
    @delf `bool` is a reserved word in C++. Adding new reserved words risks breaking backwards compatibility. So, barring great reasons, it is not done. – Yakk - Adam Nevraumont Feb 24 '15 at 23:49
  • @user2225104 `std::function` has very little to do with lambdas. A `std::function` is a generic functor which can contain anything callable, and has to use type erasure to achieve that (with associated runtime cost). The type of a lambda expression is a unique unnamed class, the "closure type." – Angew is no longer proud of SO Feb 25 '15 at 08:05
  • @Angew Try to get happy with lambdas without using std::function. Good luck with that. And that alone is proof that they did splitting and unfortunate splitting of functionality. Had they not been so clumsy regarding type of lambdas, you could actually use them without std::function. – BitTickler Feb 25 '15 at 16:16
  • @user2225104 I use lambdas *a lot,* and I have yet to use them with `std::function`. – Angew is no longer proud of SO Feb 25 '15 at 16:22

1 Answers1

5

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

Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
  • IMO it doesn't make much sense to say "`nullptr` is of type `nullptr_t`", rather than "`nullptr_t` designates the type of `nullptr`". nullptr_t is merely a typedef. – Columbo Feb 24 '15 at 13:43
  • 1
    Given that the OP's now mentioned ``, it's probably worth mentioning that that header defines `::nullptr_t` exactly the same way `` defines `::std::nullptr_t`. –  Feb 24 '15 at 13:47
  • @Columbo I believe there's a DR about that, that the definition in the standard is circular. – Angew is no longer proud of SO Feb 24 '15 at 13:49
  • @Columbo Why, the fact is `nullptr` directs to `nullptr_t` argument in overloaded functions, and it doesn't seem there's any conversion here. There are lots of typedefs in C code, but that doesn't stop us to say that, for example, "42 is of type `WORD` while -19 and 0x12345 aren't". Besides, I view both your sentences as denoting the same fact with different words: when calling `foo( nullptr )`, the very first candidate is `void foo( std::nullptr_t )`, like for `foo( 12 )` it's `void foo( int )`. – Delfigamer Feb 24 '15 at 13:56
  • @Delfigamer ... I think you missed the point. Obviously both are correct and denote the same thing, but they make differnet implications. – Columbo Feb 24 '15 at 13:57
  • @hvd Oh, great. When I include , the compiler requires std::nullptr_t. When I include , there's no type anywhere. Including defined std::nullptr_t. defines neither. – Delfigamer Feb 24 '15 at 14:23
  • @Delfigamer `` exists for stuff ported from C. Relying on a purely C++ construct such as `nullptr_t` being declared in global scope by `` is asking for trouble, IMO. It would surprise me if any standard library implementors check conformance in this particular aspect. – Angew is no longer proud of SO Feb 24 '15 at 14:25
  • @Angew @hvd Oh, I found! Including makes `void foo( nullptr_t )` compile fine. Strange. Very strange. And adding any of and before forces the type into namespace std. I almost see where this is going. – Delfigamer Feb 24 '15 at 14:32