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.


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)