'Why is an overloaded function with two arguments of type double called when passing a long long?

I wrote those two overloads:

int func(int, int) {
    return 1;
}

int func(double, double) {
    return 2;
}

When I call them with the obvious two calling schemes, i.e. func(1, 1) and func(1.0, 1.0), the first and the second overloaded functions are called, respectively, and when I try to call func(1, 1.0) it gives me an error, but when I cast the 1 to a long long, I don't get an error, and the second overload is the one called.

#include <iostream>
int main()
{
    std::cout << func(1, 1); // outputs 1.
    std::cout << func(1.0, 1.0); // outputs 2.
    // std::cout << func(1, 1.0); // erroneous.
    std::cout << func((long long)1, 1.0); // outputs 2.
}

Why is this the case? At first, I thought it was because of some promotion, but I tried a third overload with two floats and I could not get it to be called by calling it like func((int)1, 1.0f). I don't know why wouldn't it be the same, and I don't know why the second overload was called when a long long was passed.



Solution 1:[1]

Which function from an overload set is chosen to be called (i.e. overload resolution) depends (in part) on how many arguments of the function call must go through an implicit conversion, and what kind of conversion is needed.

The rules that are relevant to your example are:

For each pair of viable function F1 and F2, the implicit conversion sequences from the i-th argument to i-th parameter are ranked to determine which one is better.

F1 is determined to be a better function than F2 if implicit conversions for all arguments of F1 are not worse than the implicit conversions for all arguments of F2, and ... there is at least one argument of F1 whose implicit conversion is better than the corresponding implicit conversion for that argument of F2.

So given the overload set:

int func(int, int);        // #1
int func(double, double);  // #2 

Let's consider the following calls:

func(1, 1);    // perfect match for #1, so #1 is chosen

func(1., 1.);  // perfect match for #2, so #2 is chosen

func(1., 1);   // could call #1 by converting 1st argument to int 
               // (floating-integral conversion)

               // could call #2 by converting 2nd argument to double 
               // (floating-integral conversion)

               // error: ambiguous (equal number of conversions needed for both #1 and #2)

func(1ll, 1.); // could call #1 by converting both arguments to ints
               // (integral conversion for 1st argument, floating-integral conversion for 2nd argument)

               // could call #2 by converting just 1st argument to double
               // (floating-integral conversion for 1st argument)

               // for the 2nd parameter, #2 is ranked as a better choice, 
               // since it has a better implicit conversion sequence for #2
               // and so #2 is chosen (even though both #1 and #2 are tied for the 1st argument)

Now let's add a third overload into the mix:

int func(float, float);  // #3

Now when you make the call:

func(1, 1.f);  // could call #1 by converting 2nd argument to int
               // (floating-integral conversion for 2nd argument)

               // could call #2 by converting 1st argument to double, and converting 2nd argument to double 
               // (floating-integral conversion for 1st argument, and floating-point promotion for 2nd argument)

               // could call #3 by converting 1st argument to float  
               // (floating-integral conversion for 1st argument)

               // error: ambiguous (equal number of conversions needed for #1, #2 and #3)

Solution 2:[2]

The rules from cppreference to be applied in your case is:

For each pair of viable function F1 and F2, the implicit conversion sequences from the i-th argument to i-th parameter are ranked to determine which one is better.

F1 is determined to be a better function than F2 if implicit conversions for all arguments of F1 are not worse than the implicit conversions for all arguments of F2, and there is at least one argument of F1 whose implicit conversion is better than the corresponding implicit conversion for that argument of F2.

These pair-wise comparisons are applied to all viable functions. If exactly one viable function is better than all others, overload resolution succeeds and this function is called. Otherwise, compilation fails.

Also, there are some simple words in C++ Primer ยง6.6:

There is an overall best match if there is one and only one function for which:

  • The match for each argument is no worse than the match required by any other viable function.
  • There is at least one argument for which the match is better than the match provided by any other viable function.

If after looking at each argument there is no single function that is preferable, then the call is in error. The compiler will complain that the call is ambiguous.

using llong = long long;

void f(double, double);    // F1
void f(int, int);    // F2 

int main(void)
{
    f(double(0), double(0));
    // F1: double->double exact match; double->double exact match
    // F2: double->int conversion; double->int conversion

    // all argument of F1 is better conversion than F2, so F1 is best viable.

    f(int(0), short(0));
    // F1: int->double conversion; int->double conversion
    // F2: int->int exact match; short->int promotion

    // all argument of F2 is better conversion than F1, so F2 is best viable.

    f(short(0), char(0));
    // F1: short->double converstion; char->double converstion
    // F2: short->int promotion; char->int promotion

    // all argument of F2 is better conversion than F1, so F2 is best viable.

    f(float(0), float(0));
    //F1: float->double promotion; float->double promotion
    //F2: float->int conversion; float->int conversion

    // all argument of F1 is better conversion than F2, so F1 is best viable.

    f(double(0), int(0));
    //F1: double->double exact match; int->double converstion
    //F2 double->int converstion; int->int exact match

    // 1st argument in F1 is better conversion than that of F2, so F1 is best viable
    // 2nd argument of F2 is better conversion than that of F1, so F2 is best viable
    // F1 and F2 is better than each other, then the call is ambigious

    f(double(0), char(0));
    //F1: double->double exact match; char->double converstion
    //F2: double->int conversion; char->int promotion

    // 1st argument in F1 is better conversion than that of F2, so F1 is best viable
    // 2nd argument of F2 is better conversion than that of F1, so F2 is best viable
    // F1 and F2 is better than each other, then the call is ambigious

    f(llong(0), double(0));
    //F1: llong->double conversion; double->double exact match
    //F2: llong->int conversion; double->int conversion

    // 1st argument in F1 is equal-ranked with that of F2, 
    // 2nd argument of F1 is better conversion than that of F2, 
    // there's at least one argument of F1 is better conversion than that of F2
    // then F1 is best viable.

    f(long(0), bool(0));
    //F1: long->double conversion; bool->double conversion
    //F2: long->int conversion; bool->int promotion

    // 1st argument in F1 is equal-ranked with that of F2, 
    // 2nd argument of F1 is better conversion than that of F2, 
    // there's at least one argument of F1 is better conversion than that of F2
    // then F1 is best viable.

}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 mada