Topic: Overloading and 0


Author: sakkinen@jyu.fi (Markku Sakkinen)
Date: 5 Apr 92 15:10:12 GMT
Raw View
In article <rmartin.702315640@willing> rmartin@willing.Rational.COM (Bob Martin) writes:
>krc@wam.umd.edu (Kevin R. Coombes) writes:

[A lot deleted]

>>After all, there is only one accessible constructor; why not just use it?
>
>IMHO this would not be a good strategy.  The accessibility of a
>function is meant to prevent its use out of context, not as a
>specifier of which function to use.  It would be very very odd if A
>a(0) had two different meanings depending upon the function it was
>used in.  Moreover, if the accessibility of a function changed over
>the course of time, code which used to work could break.  e.g. by
>moving the declaration of a member function from private to public
>access, you could change the way the program works (break it.).

That's the common argument for why the principle of controlling
_access_ and not _visibility_ was chosen in C++.  Nevertheless,
in my opinion the disadvantages of this decision are greater
than the advantages.  Moving a member from private to public status
or vice versa is a change of the _interface_ of the class:
a thing that should usually be avoided and is always apt to break clients.
On the contrary, adding a new private member is something that should
_not_ disrupt anybody except the class itself and its friends;
but in C++ it does.
See my paper, "A critique of the inheritance principles of C++",
to appear in Vol. 5 No. 1 of Computing Systems, whenever it comes out.

----------------------------------------------------------------------
Don't smell rotten -- avoid a "look and smell" litigation from
Apple Computer.

Markku Sakkinen (sakkinen@jytko.jyu.fi)
       SAKKINEN@FINJYU.bitnet (alternative network address)
Department of Computer Science and Information Systems
University of Jyvaskyla (a's with umlauts)
PL 35
SF-40351 Jyvaskyla (umlauts again)
Finland
----------------------------------------------------------------------




Author: eric@tfs.com (Eric Smith)
Date: Fri, 3 Apr 1992 15:13:43 GMT
Raw View
In article <1992Apr2.153121.4155@wam.umd.edu> krc@wam.umd.edu (Kevin R. Coombes) writes:
>I just ran into a problem with the following code:
>
>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.) 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 complaint I have no problem with.
>The first one, however, keeps raising more questions the longer I think
>about it.
>
>Question 1. Which of the compilers implements the "correct" behavior?
>Here correct may be taken to mean either sanctioned by the ARM or to
>mean it ought to become part of the ANSI standard.

The first compiler was wrong, the 2nd compiler was right.  "A a(0);"
should be detected as an error.  A human reader would have trouble
deciding exactly what you meant by "A a(0);", because it looks more
like a mistake than a correct invocation of your constructor.  The
human reader, like the compiler, must always keep in mind that the
construct might be an error.

The proper solution to this problem is to make your code clearer to
both the human reader and the compiler, by putting an explicit cast on
the zero: "A a((long)0);" or "A a=(long)0;", or you can abbreviate it
in this case by using the suffix "L", e.g. "A a(0L);".




Author: rmartin@willing.Rational.COM (Bob Martin)
Date: 3 Apr 92 15:40:40 GMT
Raw View
krc@wam.umd.edu (Kevin R. Coombes) writes:

>I just ran into a problem with the following code:

>class B;

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

>A a(0);

[Two different compilers were used, one compiled OK, the other complained
that A a(0); was ambiguous.

>Question 1. Which of the compilers implements the "correct" behavior?

The compiler should complain about this.  Accessibility is not a
factor in resolving overloaded functions or constructors (ARM 13.2).
THus you have two equally valid conversions: 0->long (ARM 4.5) or
0->pointer (ARM 4.6).  The is no preference for either of these
conversions.  Thus an ambiguity exists.   (IMHO).

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

If we assume that the value NULL is a value which can be assigned to
pointers and which represents "points at nothing", then this value is
defined by the ARM in 4.6.  "A constant expresssion that evaluates to
zero...".

>Question 3. This is a different issue. Should the compiler use the
>protection status of an overloaded method to resolve ambiguities? This
>would not help oin the above example if I tried to call a(0) inside a
>member function or friend of class A, but it would help in the example.
>After all, there is only one accessible constructor; why not just use it?

IMHO this would not be a good strategy.  The accessibility of a
function is meant to prevent its use out of context, not as a
specifier of which function to use.  It would be very very odd if A
a(0) had two different meanings depending upon the function it was
used in.  Moreover, if the accessibility of a function changed over
the course of time, code which used to work could break.  e.g. by
moving the declaration of a member function from private to public
access, you could change the way the program works (break it.).

I would not support such a change...

>Thanks for your time.

>Kevin Coombes <krc@math.umd.edu>
--
+---Robert C. Martin---+-RRR---CCC-M-----M-| R.C.M. Consulting         |
| rmartin@rational.com |-R--R-C----M-M-M-M-| C++/C/Unix Engineering    |
|     (Uncle Bob.)     |-RRR--C----M--M--M-| OOA/OOD/OOP Training      |
+----------------------+-R--R--CCC-M-----M-| Product Design & Devel.   |




Author: steve@taumet.com (Steve Clamage)
Date: Fri, 3 Apr 1992 20:00:43 GMT
Raw View
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