idlmn89
idlmn89

Reputation: 367

C++ - How does the compiler decide between overloaded functions with reference types as parameter?

While studying C++ I have come across the complex topic of conversion sequences and I have encountered a problem that I couldn't solve on my own.

void g(const double)
{
    std::cout << "void g(const double)" << std::endl;
}

void g(const double&&)
{
    std::cout << "void g(const double&&)" << std::endl;
}

int main(int argc, char **argv)
{
    g(3.14);    
    return (0);
}

---------------------------- Second example ----------------------------

void g(const double)
{
    std::cout << "void g(const double)" << std::endl;
}

void g(const double&)
{
    std::cout << "void g(const double&)" << std::endl;
}

int main(int argc, char **argv)
{
    g(3.14);    
    return (0);
}

In this two examples the compiler complains about the fact that the call of the overloaded function "g(double)" is ambiguous.

void g(const double&&)
{
    std::cout << "void g(const double&&)" << std::endl;
}

void g(const double&)
{
    std::cout << "void g(const double&)" << std::endl;
}

int main(int argc, char **argv)
{
    g(3.14);    
    return (0);
}

But in this example the program compiles properly and prints out "void g(const double&&)". So I don't get why the compiler complains about the first two examples but doesn't about the third.

Upvotes: 2

Views: 1425

Answers (2)

Amir Kirsh
Amir Kirsh

Reputation: 13790

Overload resolution table

This table summarizes who can go where:

    ---------------------------------------------------------------------------------
               Caller    |   lvalue     | const lvalue |   rvalue     | const rvalue 
         Function        |              |              |              |  
    ---------------------------------------------------------------------------------
    [a]  f(X& x)         |    V (1)     |              |              |
    ---------------------------------------------------------------------------------
    [b]  f(const X& x)   |    V (2)     |      V       |    V (3)     |    V (2)
    ---------------------------------------------------------------------------------
    [c]  f(X&& x)        |              |              |    V (1)     |
    ---------------------------------------------------------------------------------
    [d]  f(const X&& x)  |              |              |    V (2)     |    V (1)
    ---------------------------------------------------------------------------------
  • All the above signatures can live together.
  • The V sign marks the possible valid resolutions
  • When there is more than one valid resolution for the same caller they are numbered, (1) being a better match than (2) etc.
  • There is no sense in overloading byval version with any of the above, unless having additional difference such as const on the method etc. Adding a byvalue version: f(X x) would not work well with any combination of the above - in most cases it would result with an ambiguity for any call, for some cases it would just prefer the byval version (if it lives only with [a] - any call except lvalue would prefer the byvalue version and an lvalue call would result with an ambiguity).
  • Signature [d] is rarely used, see: Do rvalue references to const have any use?

Upvotes: 5

M.M
M.M

Reputation: 141618

In overload resolution, direct reference binding is an identity conversion (even if qualifiers are added); it's no better or worse for a double to match a parameter of double or reference-to-double.

The const is somewhat of a red herring in your examples. For a non-reference type, f(const double), the top-level const is not part of the function signature; and in f(const double&), it is still direct binding and so still the identity conversion.

So, your first 2 cases are both identity conversions in both cases and no reason to prefer one or the other.

In case 3, rule C++14 [over.ics.rank]/3.1.3 applies:

Standard conversion sequence S1 is a better conversion sequence than standard conversion sequence S2 if

  • [...]
  • S1 and S2 are reference bindings (8.5.3) and neither refers to an implicit object parameter of a non-static member function declared without a ref-qualifier, and S1 binds an rvalue reference to an rvalue and S2 binds an lvalue reference.

This rule allows functions to be overloaded for rvalues and lvalues of the same type.

Upvotes: 3

Related Questions