2

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.

idlmn89
  • 337
  • 1
  • 3
  • 7
  • Do you realize that `void g(const double)` is the same as `void g(double)` for overload resolution purposes? I am not fully sure but I think `void g(const double&&)` is the same as `void g(double&&)` too\. – R Sahu Dec 09 '17 at 23:48
  • A literal like 3.14 [is an rvalue](http://en.cppreference.com/w/cpp/language/value_category). In overloaded context, passing an rvalue as an rvalue reference is preferred to passing an rvalue as a constant reference. Passing as a constant reference is considered to be "worse" than passing as an rvalue reference, or by value, or as a constant reference. But passing by value, as an rvalue reference, or as a constant reference is considered to be "equal" in terms of preference, hence the ambiguity. That's the short, capsule summary version. – Sam Varshavchik Dec 09 '17 at 23:49
  • @RSahu the latter two are not the same (only one of them can accept xvalue of type `const double`) – M.M Dec 09 '17 at 23:58
  • @M.M, thanks for the clarification. – R Sahu Dec 10 '17 at 00:09
  • In principle, once you have a function receiving an object by value, say `void f(T)`, and you decide to overload `f` to be able to take such object by reference, say `void f(T&)` - You will create rooms for ambiguities to arise during overload resolution. You generally want to avoid such API's. You generally want to overload based on *rvalue* and *(const) lvalue* references or just pass *by value*, but not both. – WhiZTiM Dec 10 '17 at 00:14

2 Answers2

4

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?
Amir Kirsh
  • 12,564
  • 41
  • 74
3

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.

M.M
  • 138,810
  • 21
  • 208
  • 365