Back to Item 24: Choose carefully between function overloading and parameter defaulting.
Continue to Item 26: Guard against potential ambiguity.

Item 25: Avoid overloading on a pointer and a numerical type.

Trivia question for the day: what is zero?

More specifically, what will happen here?

    void f(int x);
    void f(string *ps);

    f(0);                        // calls f(int) or f(string*)?

The answer is that `0` is an `int` — a literal integer constant, to be precise — so `f(int)` will always be called. Therein lies the problem, because that's not what people always want. This is a situation unique in the world of C++: a place where people think a call should be ambiguous, but compilers do not.

It would be nice if you could somehow tiptoe around this problem by use of a symbolic name, say, `NULL` for null pointers, but that turns out to be a lot tougher than you might imagine.

Your first inclination might be to declare a constant called `NULL`, but constants have types, and what type should `NULL` have? It needs to be compatible with all pointer types, but the only type satisfying that requirement is `void*`, and you can't pass `void*` pointers to typed pointers without an explicit cast. Not only is that ugly, at first glance it's not a whole lot better than the original situation:

    void * const NULL = 0;             // potential NULL definition

    f(0);                              // still calls f(int)
    f(static_cast<string*>(NULL));     // calls f(string*)
    f(static_cast<string*>(0));        // calls f(string*)

On second thought, however, the use of `NULL` as a `void*` constant is a shade better than what you started with, because you avoid ambiguity if you use only `NULL` to indicate null pointers:

    f(0);                              // calls f(int)
    f(NULL);                           // error! — type mis-match
    f(static_cast<string*>(NULL));     // okay, calls f(string*)

At least now you've traded a runtime error (the call to the "wrong" `f` for 0) for a compile-time error (the attempt to pass a `void*` into a `string*` parameter). This improves matters somewhat (see Item 46), but the cast is still unsatisfying.

If you shamefacedly crawl back to the preprocessor, you find that it doesn't really offer a way out, either, because the obvious choices seem to be

    #define NULL 0

and

    #define NULL ((void*) 0)

and the first possibility is just the literal `0`, which is fundamentally an integer constant (your original problem, as you'll recall), while the second possibility gets you back into the trouble with passing `void*` pointers to typed pointers.

If you've boned up on the rules governing type conversions, you may know that C++ views a conversion from a `long` `int` to an `int` as neither better nor worse than a conversion from the `long` `int` `0` to the null pointer. You can take advantage of that to introduce the ambiguity into the `int`/pointer question you probably believe should be there in the first place:

    #define NULL 0L            // NULL is now a long int

    void f(int x);
    void f(string *p);

    f(NULL);                   // error! — ambiguous

However, this fails to help if you overload on a `long` `int` and a pointer:

    #define NULL 0L

    void f(long int x);        // this f now takes a long
    void f(string *p);

    f(NULL);                   // fine, calls f(long int)

In practice, this is probably safer than defining `NULL` to be an `int`, but it's more a way of moving the problem around than of eliminating it.

The problem can be exterminated, but it requires the use of a late-breaking addition to the language: member function templates (often simply called member templates). Member function templates are exactly what they sound like: templates within classes that generate member functions for those classes. In the case of `NULL`, you want an object that acts like the expression `static_cast<T*>(0)` for every type `T`. That suggests that `NULL` should be an object of a class containing an implicit conversion operator for every possible pointer type. That's a lot of conversion operators, but a member template lets you force C++ into generating them for you:

    // a first cut at a class yielding NULL pointer objects
    class NullClass {
    public:
      template<class T>                       // generates
        operator T*() const { return 0; }     // operator T* for
    };                                        // all types T; each
                                              // function returns
                                              // the null pointer

    const NullClass NULL;             // NULL is an object of
                                      // type NullClass

    void f(int x);                    // same as we originally had

    void f(string *p);                // ditto

    f(NULL);                          // fine, converts NULL to
                                      // string*, then calls f(string*)

This is a good initial draft, but it can be refined in several ways. First, we don't really need more than one `NullClass` object, so there's no reason to give the class a name; we can just use an anonymous class and make `NULL` of that type. Second, as long as we're making it possible to convert `NULL` to any type of pointer, we should handle pointers to members, too. That calls for a second member template, one to convert `0` to type `T` `C::*` ("pointer to member of type `T` in class `C`") for all classes `C` and all types `T`. (If that makes no sense to you, or if you've never heard of — much less used — pointers to members, relax. Pointers to members are uncommon beasts, rarely seen in the wild, and you'll probably never have to deal with them. The terminally curious may wish to consult Item 30, which discusses pointers to members in a bit more detail.) Finally, we should prevent clients from taking the address of `NULL`, because `NULL` isn't supposed to act like a pointer, it's supposed to act like a pointer value, and pointer values (e.g., 0x453AB002) don't have addresses.

The jazzed-up `NULL` definition looks like this:

    const                             // this is a const object...
    class {
    public:
      template<class T>               // convertible to any type
        operator T*() const           // of null non-member
        { return 0; }                 // pointer...

      template<class C, class T>      // or any type of null
        operator T C::*() const       // member pointer...
        { return 0; }

    private:
      void operator&() const;         // whose address can't be
                                      // taken (see Item 27)...

    } NULL;                           // and whose name is NULL

This is truly a sight to behold, though you may wish to make a minor concession to practicality by giving the class a name after all. If you don't, compiler messages referring to `NULL`'s type are likely to be pretty unintelligible.

For another example of how member templates can be useful, take a look at Item M28.

An important point about all these attempts to come up with a workable `NULL` is that they help only if you're the caller. If you're the author of the functions being called, having a foolproof `NULL` won't help you at all, because you can't compel your callers to use it. For example, even if you offer your clients the space-age `NULL` we just developed, you still can't keep them from doing this,

    f(0);                  // still calls f(int),
                           // because 0 is still an int

and that's just as problematic now as it was at the beginning of this Item.

As a designer of overloaded functions, then, the bottom line is that you're best off avoiding overloading on a numerical and a pointer type if you can possibly avoid it.

Back to Item 24: Choose carefully between function overloading and parameter defaulting.
Continue to Item 26: Guard against potential ambiguity.