Topic: Overloading Resolution


Author: hitz@csi.uottawa.ca (Martin Hitz)
Date: 26 Jun 91 14:05:08 GMT
Raw View
In the course of testing my understanding of the rules for argument matching
given in section 13.2 in the ARM, I tried to predict the outcome of the
program given below. In order to verify my conclusions, I tried the
program with 4 different compilers (GNU g++, Sun's cfront, Zortech, and
Borland C++). As the results have been somewhat arbitrary, I'd like to
get your comments on who's right:

#include <stream.h>

void f (const double &, double, void *)  { cout << 1; }
void f (double &, int, const char * = 0) { cout << 2; }
void f (int)      { cout << 3; }
void f (long, int = 0)     { cout << 4; }

main ()
{
 double b;
 const double pi = 3.14;

        // Z g++ cfront BC++
        //---------------------------------------
 f(1.1, 1, f);  // 1 err err 1
 f(1, 1, "Y"); // err err err 2
 f(3, 1.1, 0);  //  err err 1 err
 f(1.1, 3, (void*)0);// err 1 1 err
 f(1, 1); //  err 4 4 4
 f(pi, pi, "Y"); // 1 1 1 err
 f(0L);   //  4 3 4 4
 f(0L, 'a'); // err 4 4 4
 f(pi);  // err 3 err err
}

Thanks, Martin Hitz@csi.uottawa.ca




Author: jangr@microsoft.UUCP (Jan GRAY)
Date: 4 Jul 91 02:56:48 GMT
Raw View
In article <1991Jun26.140508.19662@csi.uottawa.ca> hitz@csi.uottawa.ca (Martin Hitz) writes:
>In the course of testing my understanding of the rules for argument matching
>given in section 13.2 in the ARM, I tried to predict the outcome of the
>program given below. In order to verify my conclusions, I tried the
>program with 4 different compilers (GNU g++, Sun's cfront, Zortech, and
>Borland C++). As the results have been somewhat arbitrary, I'd like to
>get your comments on who's right:


OK.  Here goes...

>#include <stream.h>
>
>void f (const double &, double, void *)         { cout << 1; }
>void f (double &, int, const char * = 0)        { cout << 2; }
>void f (int)                                    { cout << 3; }
>void f (long, int = 0)                          { cout << 4; }
>
>main ()
>{
>        double b;
>        const double pi = 3.14;
>
>                        //      Z       g++     cfront  BC++
>                        //---------------------------------------


>        f(1.1, 1, f);   //      1       err     err     1

    Should be error.

    Because there are three actuals, only f[1] and f[2] could be candidates.
    However, there is no conversion from the actual expression "f" (overloaded)
    to either of "void *" or "const char *".

    13.2: "...A call of a given function name chooses, from among all functions
    by that name that are in scope and for which a set of conversions exists so
    that the function could possibly be called ..."

    For this set of actuals, no function could possibly be called.



>        f(1, 1, "Y");   //      err     err     err     2

    Everybody's wrong.  The right answer is f[1]!

    Again, there are three actuals so only f[1] or f[2] could be candidates.
    However, f[2] could not possibly be called because it is illegal in
    C++ 2.1 to initialize a "double &" formal parameter from an actual "1".
    See 8.4.3: "A reference to a plain T can be initialized only with a
    plain T."  The actual "1" has type int which is not a plain double.
    (Perhaps your cfront is pre 2.1?)

    Once again,
    13.2: "...A call of a given function name chooses, from among all functions
    by that name that are in scope and for which a set of conversions exists so
    that the function could possibly be called ..."

    Here the only function that could possibly be called is f[1].
    That's the one which should be called.



>        f(3, 1.1, 0);   //      err     err     1       err

    f[1] should be called.

    Again, there are three actuals so only f[1] or f[2] could be candidates.
    By the explanation above, f[2] is not a candidate.
    Since a set of conversions exist which permits f[1] to be called, it
    should be called, just as explained in the previous case.



>        f(1.1, 3, (void*)0);//  err     1       1       err

    f[1] should be called.

    Again, there are three actuals so only f[1] or f[2] could be candidates.
    Conversions exist to call f[1] so it is a candidate.
    However, f[2] cannot be called because there is no conversion
    from "void *" to "const char *" in C++.  Thus f[1] is the only
    candidate thus, by the first paragraph of 13.2 it should be called.



>        f(1, 1);        //      err     4       4       4

    f[4] should be called.  Only f[4] has the right number of formals
    and conversions exist to call it.  (f[2] can't be called, there's
    no conversion from 1 to "double&".)



>        f(pi, pi, "Y"); //      1       1       1       err

    Error, ambiguous.  Both f[1] and f[2] have the right number of
    formals and the actuals can be converted to the formals for both
    f[1] and f[2].  Finally, we have to start disambiguating by
    considering costs of conversions.

    Parameter 1:
        f[1]:   pi      -> double&          exact/match with trivial convs
        f[2]:   pi      -> const double&    exact/match with trivial convs
    Parameter 1 best match set: { f[1], f[2] }.

    Parameter 2:
        f[1]:   pi      -> double           exact/match with trivial convs
        f[2]:   pi      -> int              match with standard conversions
    Parameter 2 best match set: { f[1] }.

    Parameter 3:
        f[1]:   "Y"     -> void *           match with standard conversions
        f[2]:   "Y"     -> const char *     exact/match with trivial convs
    ("Y" is of type array of char;
     this is trivially promoted to pointer to char
     this is trivially promoted to pointer to const char)
    Parameter 3 best match set: { f[2] }.

    Intersection of best match sets: { f[1], f[2] } ^ { f[1] } ^ { f[2] } = nil
    Since intersection is nil (empty), call is ambiguous.



>        f(0L);          //      4       3       4       4

    f[4] should be called.  The two candidates f[3] and f[4] have appropriate
    numbers and types of arguments.  Disambiguate by cost of conversions:

    Parameter 1:
        f[3]:   0L      -> int              match with standard conversions
        f[4]:   0L      -> long             exact/match with trivial convs
    Parameter 1  best match set: { f[4] }.

    Thus f[4] should be called.



>        f(0L, 'a');     //      err     4       4       4

    f[4] should be called.  The two candidates with 2 arguments are
    f[2] and f[4].  However, there is no conversion from 0L to "double&"
    (see 8.4.3 again).  Thus the only function with right numbers of
    arguments and with conversions from actuals to formals is f[4].


>        f(pi);          //      err     3       err     err

    Error, ambiguous.

    Two possible functions, f[3] and f[4], both of which have right
    numbers of arguments, and conversions exist from pi to their
    formal types.  Disambiguate by cost of conversions:

    Parameter 1:
        f[3]:   pi      -> int              match with standard conversions
        f[4]:   pi      -> long             match with standard conversions
    Parameter 1 best match set: { f[3], f[4] }.

    Intersection of best match sets for this (one) argument yields
    { f[3], f[4] }.  13.2: "Unless this intersection has exactly one member,
    the call is illegal.



The current overloaded function disambiguation rules are anything but
intuitive!  Just wait til X3J16 tries to specify the rules for
disambiguating amongst overloaded operator functions and builtin
operator functions on objects when user defined conversion
functions are applied...

Cheers.

Jan Gray  jangr@microsoft.com  Microsoft Corp., Redmond, WA  206-882-8080




Author: jangr@microsoft.UUCP (Jan GRAY)
Date: 4 Jul 91 05:45:25 GMT
Raw View
Silly me.  I forgot that some of the compilers you are trying are
C++ 2.0 compilers.  One of the differences between 2.0 and 2.1
is under C++ 2.1, it is not legal to initialize a T& with a
non-T lvalue.

So....

This posting will discuss which way ambiguities are resolved under
C++ *2.0* conversion rules.  (Unfortunately, some of the functions
will disambiguate differently under 2.0 than under 2.1.)

>>#include <stream.h>
>>
>>void f (const double &, double, void *)         { cout << 1; }
>>void f (double &, int, const char * = 0)        { cout << 2; }
>>void f (int)                                    { cout << 3; }
>>void f (long, int = 0)                          { cout << 4; }
>>
>>main ()
>>{
>>        double b;
>>        const double pi = 3.14;
>>
>>                        //      Z       g++     cfront  BC++
>>                        //---------------------------------------
>
>
>>        f(1.1, 1, f);   //      1       err     err     1
>
>    Should be error.
        Error under C++ 2.0 also.



>
>>        f(1, 1, "Y");   //      err     err     err     2
>
>    Everybody's wrong.  The right answer is f[1]!

        Under C++ 2.0, f[2] should be called:

        Under C++ 2.0, there are two valid candidates, since both f[1] and
        f[2] might be called with the given arguments.  Disambiguation:

        Parameter 1:
            f[1]:   1   -> const double &       promotion
            f[2]:   1   -> double&              promotion
        Parameter 1 best match set: { f[1], f[2] }

        Parameter 2:
            f[1]:   1   -> double               promotion
            f[2]:   1   -> int                  exact/trivial convs
        Parameter 3 best match set: { f[2] }

        Parameter 3:
            f[1]:   "Y" -> void *               standard conversion
            f[2]:   "Y" -> const char *         exact/trivial convs
        Parameter 3 best match set: { f[2] }

        Intersection of these sets indicates f[2] should be called.



>>        f(3, 1.1, 0);   //      err     err     1       err
>
>    f[1] should be called.

        Under C++ 2.0, f[1] should be called (but for different reasons):

        Under C++ 2.0, there are two valid candidates, since both f[1] and
        f[2] might be called with the given arguments.  Disambiguation:

        Parameter 1:
            f[1]:   3   -> const double &       promotion
            f[2]:   3   -> double&              promotion
        Parameter 1 best match set: { f[1], f[2] }

        Parameter 2:
            f[1]:   1.1 -> double               exact/trivial convs
            f[2]:   1.1 -> int                  standard conversion
        Parameter 3 best match set: { f[1] }

        Parameter 3:
            f[1]:   0   -> void *               standard conversion
            f[2]:   0   -> const char *         standard conversion
        Parameter 3 best match set: { f[1], f[2] }

        Intersection of these sets yields { f[1] } so f[1] should be called.



>>        f(1.1, 3, (void*)0);//  err     1       1       err
>
>    f[1] should be called.

        Same under C++ 2.0.


>
>>        f(1, 1);        //      err     4       4       4

        Under C++ 2.0, this is an ambiguous call:

        Under C++ 2.0, both f[2] and f[4] are candidates.

        Parameter 1:
            f[2]:   1   -> const double &   promotion
            f[4]:   1   -> long             promotion
        Parameter 1 best match set { f[2], f[4] }.

        Parameter 2:
            f[2]:   1   -> int              exact/match with trivial convs
            f[4]:   1   -> int              exact/match with trivial convs
        Parameter 2 best match set { f[2], f[4] }.

        Interesection yields { f[2], f[4] }.  Error, ambiguous.

>
>>        f(pi, pi, "Y"); //      1       1       1       err
>
>    Error, ambiguous.

        Same under C++ 2.0.


>>        f(0L);          //      4       3       4       4
>
>    f[4] should be called.

        Same under C++ 2.0.



>>        f(0L, 'a');     //      err     4       4       4
>
>    f[4] should be called.  The two candidates with 2 arguments are
>    f[2] and f[4].

        Under C++ 2.0, f[4] should be called:

        Under C++ 2.0, both f[2] and f[4] are candidates.

        Parameter 1:
            f[2]:   0L  -> const double &   promotion
            f[4]:   0L  -> long             exact/match with trivial convs
        Parameter 1 best match set { f[4] }.

        Parameter 2:
            f[2]:   'a' -> int              promotion
            f[4]:   'a' -> int              promotion
        Parameter 2 best match set { f[2], f[4] }.

        Interesection yields { f[4] }.  f[4] is called.
>
>
>>        f(pi);          //      err     3       err     err
>
>    Error, ambiguous.

        Same under C++ 2.0.



Jan.