From -2699747781943319252
X-Google-Language: ENGLISH,ASCII-7-bit
X-Google-Thread: 109fba,388934337992c65c
X-Google-Attributes: gid109fba,public
X-Google-Thread: f78e5,388934337992c65c
X-Google-Attributes: gidf78e5,public
X-Google-ArrivalTime: 1992-04-03 12:00:43 PST
Xref: sparky comp.std.c++:366 comp.lang.c++:6481
Newsgroups: comp.std.c++,comp.lang.c++
Path: sparky!uunet!taumet!steve
From: steve@taumet.com (Steve Clamage)
Subject: Re: Overloading and 0
Message-ID: <1992Apr3.200043.24368@taumet.com>
Keywords: overloading, NULL, ambiguity
Organization: TauMetric Corporation
References: <1992Apr2.153121.4155@wam.umd.edu>
Date: Fri, 3 Apr 1992 20:00:43 GMT

krc@wam.umd.edu (Kevin R. Coombes) writes:

>class B;

>class A {
>  private:
>    A(B *);
>  public:
>    A(long);
>};

>A a(0);

>(This is, of course, a simplified example.) I used two different
>compilers. The first compiler did what I expected. It silently
>compiled the definition of the object "a" using the constructor
>A(long), and went on to compute the correct answer. (I know it was
>correct; this was a test of the class.)

Sorry, it is not "correct".  This is code ambiguous, since there is no
way to decide between a conversion from a literal zero to a long or to
a pointer.  In such cases, you have to provide an explicit cast to
indicate which of the two you want:
	A a((long)0);
	A a((B*)0);

This is an example of the more general problem of providing overloaded
functions taking scalar arguments, but none of them type int.  A
common error is something like
	foo(short);
	foo(double);
	...
	foo(5);
There are standard conversions from 5 (which is type int) to both
short and double, and there is no way to choose between them.  (If
you think it is "obvious" which one is right, you need to think
about the problem some more.)  It is usually better to have one of
the functions take an int, so that calls with literal values (or with
expression of type int) are not ambiguous.  In my example, foo(5)
would be an exact match to foo(int), so there would be no ambiguity --
an exact match is preferred over a standard conversion.

>The second compiler refused to compile. It claimed two errors at this line.
>First, it claimed the call was ambiguous, since it could not decide
>which constructor to use. Second, it pointed out that the constructor
>A(B *) was not accesible.

The second compiler is correct.  The first compiler is in error.  The
rule is that overloading is resolved independent of access rules, then
access to the selected function is checked.  Example:
	class A {
	    foo(int);
	public:
	    foo(double);
	};

	foo(1);		// error, foo(int) inaccesible
	foo(1.0);	// ok, foo(double) is accessible
This rule prevents surprising behavior when different overloaded
functions would be selected depending on the access rights where
the call occurs.  Thus "foo(1)" always refers to the same function,
or is always ambiguous, no matter where the call appears.

This is all explained at length in the ARM, and in other books.

>Question 2. How does the answer to this question affect the correct
>definition of NULL?

In C++, the best definition of NULL is usually plain literal 0,
because there is no automatic cast from void* to other pointer types.
That is, we always want to be able to say for any type T
	T* p = NULL;
and we can't if NULL is type void*.  We can if NULL is 0.

If you are concerned about picking the correct overloaded function
when using NULL, and it matters whether NULL is 0 or 0L, then don't
rely on using NULL.  Use an explicit 0 or 0L instead.  (The
applicable rule is "Don't depend on implementation-defined features
when you don't have to.")

NULL is not much needed in C++, since functions must be prototyped.
When passing a parameter to the ellipsis in a function (where there
is no prototype) always explicitly cast a zero to the type the
function expects.  This is as true in C as in C++.
-- 

Steve Clamage, TauMetric Corp, steve@taumet.com
Vice Chair, ANSI C++ Committee, X3J16


