More C++ Idioms/nullptr
From Wikibooks, the open-content textbooks collection
Contents |
[edit]
nullptr
[edit] Intent
To distinguish between an integer 0 and a null pointer.
[edit] Also Known As
[edit] Motivation
For many years C++ had an embarrassment of not having a keyword to designate a null pointer. The upcoming C++ standard, C++0x, promises to eliminate the embarrassment. C++ can't use NULL macro of C because of strong type checking of C++ makes it almost useless in expressions as below.
#define NULL ((void *)0) std::string * str = NULL; // Can't automatically cast void * to std::string * void (C::*pmf) () = &C::func; if (pmf == NULL) {} // Can't automatically cast from void * to pointer to member function.
So C++ uses literal integer 0 to designate so called null pointer. It works in overwhelmingly large number of cases but sometimes can be confusing in the presence of overloaded functions. For example, the func(int) overload below takes the precedence because the type of literal 0 is int.
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 }
More confusion arises when NULL macro is used. 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 }
[edit] Solution and Sample Code
nullptr idiom solves some of the above problems in a library of null pointer. A recent draft proposal (N2431) by Herb Sutter and Bjarne Stroustrup recommends that a new keyword nullptr be added to C++. nullptr idiom is the closest match possible today using existing C++ features. The following nullptr implementation is a variant of the library based approach suggested by Scott Meyer in his book More Effective C++.
#include <typeinfo> const // It is a const object... class nullptr_t { public: template<class T> operator T*() const // convertible to any type of null non-member pointer... { return 0; } template<class C, class T> operator T C::*() const // or any type of null member pointer... { return 0; } private: void operator&() const; // Can't take address of nullptr } nullptr = {}; 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 due to bug #33990 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; // ch1 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.
[edit] Consequences
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 idiom. That means it is clear that language does not have a first class keyword for null pointer.
- Compilers can’t produce meaningful error messages when nullptr is implemented as a library.