Topic: Overload resolution errors


Author: lincmad@netcom.com (Linc Madison)
Date: Mon, 4 Apr 1994 00:25:42 GMT
Raw View
[I originally posted this in comp.lang.c++, but I think it is appropriate
here, since my question deals with the way the C++ standard resolves
overloaded functions.]

I've been looking into the rules for resolving overloaded function
references.  I started out looking at the situation vis-a-vis long
doubles, which doesn't seem to have been of interest to many people,
since LD's are seldom used.  However, in the course of looking into this
morass, I have discovered that both of the compilers I have access to
(GNU g++ 2.5.8 and Symantec C++ 6.0.1/Mac) take an approach to overload
resolution that I find entirely sensible, but which is at the same time
clearly in violation of ARM 13.2.  (I have included sample code at the
bottom which I encourage you to try on other compilers.)

Specifically, I'm referring to rule [2] in section 13.2 of ARM, p. 312ff.
The annotation on page 321 states "this rule exists to allow promotions
to be preferred over standard conversions."

What a great many people -- evidently including the compiler writers at
both Symantec and GNU -- don't realize is that this rule encompasses
ONLY the following promotions:
* { signed | unsigned } { char | short } --> { signed | unsigned } int
(a subset of all the possibilities encompassed above, with some
dependency on the implementation; refer to section 4.1)
* float --> double

The annotation on p.323 states that "There was serious discussion of
preferring value-preserving conversions to potentially value-destroying
conversions beyond the distinction between promotions and conversions.
... failed for several reasons: what is value-destroying depends
critically on the actual value ... [and] the implementation."

However, there are numerous conversions that can reasonably be considered
completely portable promotions, that are not included in rule [2] as it
currently stands.  For example:
{char, short, int} --> long int
{float, double} --> long double

There are implementation dependencies involved in some conversions, such
as unsigned int to signed long int (safe iff sizeof(long) > sizeof(int),
which the standard only guarantees as greater than or equal).

Take a look at a few pathological overload instances, repeated from one
of my earlier posts:

// Set #1
void foo(int, int, char);
void foo(int, long, long double);
int n; double d;
foo(n, n, d);  // calls foo(int, int, char) instead of flagging ambiguity
               // because 2nd arg is exact match and double -> char is
               // on equal footing with double -> long double

// Set #2
void foo(int, int, char);
void foo(int, int, long double);
int n; double d;
foo(n, n, d);  // flags as ambiguous instead of calling f(i,i,ld)
               // (flip-side of the same coin)

// Set #3
void foo(short);
void foo(int);
char c;
foo(c);  // calls foo(int) instead of flagging ambiguity or using foo(short)

I think the following set of rules, with some elaboration concerning
signed/unsigned integer promotions, would result in a much simpler
system.  The only ambiguities introduced are ones that I believe the
compiler *should* flag, such as Set #1 above.  Even using only the
promotions shown in the diagram on ARM page 41 would be a major
improvement over the current system.

[1] trivial promotions
[2] all value-preserving integral and floating-point promotions
    (in case of a tie, the promotion to the smallest type wins --
    i.e., float -> double beats float -> long double)
[3] all other standard conversions (e.g., int -> float, int -> short)
[4] user-defined conversions
[5] ellipsis

At the very least, the annotation in ARM p. 321 needs to be revised,
because the logic given for the choice of rules contradicts the rules
themselves.  Personally, I think the rules should be changed to match
the annotation, rather than vice-versa.

I tried the following sample code in both Symantec C++ 6.0.1 (Mac) and
g++ 2.5.8:

//----------------
#include <iostream.h>

void foo(short s) { cout << "short " << s << endl; }
void foo(int n)   { cout << "int   " << n << endl; }

main()
{
        char c = 22;
        foo(c); // Symantec flags ambiguous, g++ calls foo(short)
                // ARM 13.2 requires foo(int)
        return 1;
}
//---------------
#include <iostream.h>

void foo(short s)        { cout << "short " << s  << endl; }
void foo(long double ld) { cout << "l-dbl " << ld << endl; }

main()
{
        double d = 26.7;
        foo(d); // both Symantec and g++ call foo(long double)
                // ARM 13.2 requires "ambiguous"
        return 1;
}
//---------------
void foo(long li) { cout << "long  " << li << endl; }
void foo(float f) { cout << "float " << f  << endl; }

main()
{
        char c = 22;
        int n = 22;
        foo(c); // both Symantec and g++ call foo(long) in both cases
        foo(n); // ARM 13.2 requires "ambiguous" in both cases.
        return 1;
}
//-------------

Clearly, there is confusion regarding the rules, and with good reason.
I think that preferring all the promotions in the diagram on page 41
over all other standard conversions (except those mentioned in section
4.1), would produce a clearer, simpler, and better system than that
currently in use.  It need not introduce any additional implementation
dependencies (e.g., unsigned short -> int would be a "conversion" rather
than a "promotion").

If anyone can see problems with this system that would make it less
workable than the current rules, please let me know.

-- Linc Madison   *   Oakland, California   *   LincMad@Netcom.com