More C++ Idioms/nullptr

nullptrEdit

IntentEdit

To distinguish between an integer 0 and a null pointer.

Also Known AsEdit

MotivationEdit

For many years C++ had an embarrassment of not having a keyword to designate a null pointer. C++11 has eliminated that embarrassment. C++'s strong type checking makes C's NULL macro almost useless in expressions, e.g.:

// if the following were a valid definition of NULL in C++
#define NULL ((void *)0)
 
// then...
 
char * str = NULL; // Can't automatically convert void * to char *
void (C::*pmf) () = &C::func;
if (pmf == NULL) {} // Can't automatically convert from void * to pointer to member function.

Instead,

#define NULL 0

or

#define NULL 0L

are valid definitions for NULL in C++. See below.

The crux of the matter, in fact, is that C++ disallows conversions from void *, even when the value is a constant zero, but, for constant zero, introduces a special case anyway: int to pointer (actually several of them: short to pointer, long to pointer, etc.). This is, in fact, even worse than allowing (for the constant case) the former exception, the more so given the support of function overloading in the language.

void func(int);
void func(double *);
int main()
{
  func (static_cast <double *>(0)); // calls func(double *) as expected
  func (0); // calls func(int) but the programmer might have intended double *, because 0 IS also a null pointer constant (or the reader might be misled).
}

The use of the macro NULL has its own set of problems as well. C++ requires that macro NULL be defined as an integral constant expression having the value of 0. So unlike in C, NULL cannot be defined as (void *)0 in the C++ standard library. Furthermore, the exact form of definition is left to the particular implementation, which means that e.g. both 0 and 0L are viable options, among some others. This is a trouble as it can cause confusion in overload resolution. Worse, the way confusing overload resolution manifests itself will vary depending on the compiler and its settings used. An illustrative case is shown in this slight modification of the example above:

#include <cstddef>
 
void func(int);
void func(double *);
 
int main()
{
  func (static_cast <double *>(0)); // calls func(double *) as expected
  func (0); // calls func(int) but double * may be desired because 0 IS also a null pointer
 
  func (NULL) // calls func(int) if NULL is defined as 0 (confusion, func(double *) was intended!) - logic error at runtime,
              // but the call is ambiguous if NULL is defined as 0L (yet more confusion to the unwary!) - compilation error
}

Solution and Sample CodeEdit

The nullptr idiom solves some of the above problems and can be put in reusable form, as a library solution. It is a very close approximation of a "null keyword", by using only pre-C++11 standard features.

The following is such a library solution as mostly found in Effective C++ by Scott Meyers, Second Edition, Item 25 (it is not present in the third edition of the book).

const // It is a const object...
class nullptr_t 
{
  public:
    template<class T>
    inline operator T*() const // convertible to any type of null non-member pointer...
    { return 0; }
 
    template<class C, class T>
    inline operator T C::*() const   // or any type of null member pointer...
    { return 0; }
 
  private:
    void operator&() const;  // Can't take address of nullptr
 
} nullptr = {};

The following code illustrates some usage cases (and assumes the class template above has already been #included).

#include <typeinfo>
 
struct C
{
  void func();
};
 
template<typename T> 
void g( T* t ) {}
 
template<typename T> 
void h( T t ) {}
 
void func (double *) {}
void func (int) {}
 
int main(void)
{
  char * ch = nullptr;        // ok
  func (nullptr);             // Calls func(double *)
  func (0);                   // Calls func(int)
  void (C::*pmf2)() = 0;      // ok
  void (C::*pmf)() = nullptr; // ok
  nullptr_t n1, n2;
  n1 = n2;
  //nullptr_t *null = &n1;    // Address can't be taken.
  if (nullptr == ch) {}       // ok
  if (nullptr == pmf) {}      // Valid statement; but fails on g++ 4.1.1-4.5 due to bug #33990
// for GCC 4: if ((typeof(pmf))nullptr == pmf) {}
  const int n = 0;
  if (nullptr == n) {}        // Should not compile; but only Comeau shows an error.
  //int p = 0;
  //if (nullptr == p) {}      // not ok
  //g (nullptr);              // Can't deduce T
  int expr = 0;
  char* ch3 = expr ? nullptr : nullptr; // ch3 is the null pointer value
  //char* ch4 = expr ? 0 : nullptr;     // error, types are not compatible
  //int n3 = expr ? nullptr : nullptr;  // error, nullptr can’t be converted to int
  //int n4 = expr ? 0 : nullptr;        // error, types are not compatible
 
  h( 0 );                // deduces T = int
  h( nullptr );          // deduces T = nullptr_t
  h( (float*) nullptr ); // deduces T = float*
 
  sizeof( nullptr );     // ok
  typeid( nullptr );     // ok
  throw nullptr;         // ok
}

Unfortunately, there seems to be a bug in gcc 4.1.1 compiler that does not recognize the comparison of nullptr with point to member function (pmf). The above code compiles on VC++ 8.0 and Comeau 4.3.10.1 beta.

Note that nullptr idioms makes use of the Return Type Resolver idiom to automatically deduce a null pointer of the correct type depending upon the type of the instance it is assigning to. For example, if nullptr is being assigned to a character pointer, a char type instantiation of the templatized conversion function is created.

ConsequencesEdit

There are some disadvantages of this technique and are discussed in the N2431 proposal draft. In summary, the disadvantages are

  • A header must be included to use nullptr. In C++11, nullptr itself is a keyword and requires no headers (although std::nullptr_t does).
  • Compilers have historically produced (arguably) unsatisfactory diagnostics when using the code proposed.

Known UsesEdit

Related IdiomsEdit

ReferencesEdit

Last modified on 31 July 2013, at 06:10