Normal OR Rules for Reference Bindings
What are the ranking rules for reference bindings? Let’s consider the following example:
void f(int); // #1
void f(int&); // #2
void f(int const&); // #3
void f(int&&); // #4
void f(int const&&); // #5
Here are some key points:
#1
is a function that accepts borrowed int arguments.#2
is a function that takes an lvalue reference.#3
is a function that takes a const lvalue reference, which accepts both lvalue and xvalue arguments.#4
is a function that takes an rvalue reference.#5
is a function that takes a const rvalue reference, which accepts an xvalue with a const qualifier.- All references bind to lvalues/xvalues.
- Numeric literals are non-const.
Case 1 (#1 and #2)
int x = 1;
f(x); // ambiguous!
It’s ambiguous because binding to a reference of the correct type is treated as an exact match, just like binding to a non-reference of the correct type.
To avoid ambiguity, we have a few tricks:
f(+x); // calls #1 by using the unary plus
f(int(x)); // calls #1 by explicitly copying the object
f(const_cast<int const&>(x)); // calls #1 by using const_cast
f(std::cref(x)); // calls #1 by using std::cref
f(static_cast<int&&>(x)); // calls #1 by casting the argument type
static_cast<void(*)(int)>(f)(x); // calls #1 by casting the function type
static_cast<void(*)(int&)>(f)(x); // calls #2 by casting the function type
Case 2 (#1, #2, and #3)
int x = 1;
f(x); // ambiguous!
To avoid ambiguity, we must cast the function type(best avoided):
static_cast<void(*)(int)>(f)(x); // calls #1 by casting the function type
static_cast<void(*)(int&)>(f)(x); // calls #2 by casting the function type
Case 3 (#2 and #3)
#2
is the better choice for lvalue references, while #3
is preferable for const lvalue references. The prvalue is materialized and the const lvalue reference binds it.
int x = 1;
int const& y = x;
f(x); // calls #2
f(+x); // calls #3
f(const_cast<int const&>(x)); // calls #3
f(1); // calls #3
f(y); // calls #3
Case 4 (#1 and #3)
There is absolutely no way to distinguish between #1
and #3
for the compiler.
int x = 1;
int const& y = x;
f(x); // ambiguous
f(y); // ambiguous
f(const_cast<int const&>(x)); // ambiguous
f(+x); // ambiguous
f(static_cast<int&&>(x)); // ambiguous
f(1); // ambiguous
static_cast<void(*)(int)>(f)(x); // calls #1
static_cast<void(*)(int const&)>(f)(x); // calls #3
Case 5 (#3 and #5)
The rank of #5
is better than the rank of #3
for rvalues.
f(x); // calls #3
f(+x); // calls #5
f(static_cast<int&&>(x)); // calls #5
f(const_cast<int const&>(x)); // calls #3
f(int(x)); // calls #5
static_cast<void(*)(int const&)>(f)(x); // calls #3
static_cast<void(*)(int const&&)>(f)(x); // error! cannot bind rvalue reference to lvalue
Case 6 (#3 and #4)
The behavior is similar to Case 5.
Case 7 (#4 and #5)
#5
is less good than #4
because binding int const&&
to xvalues requires a qualification conversion.
int x = 1;
int const&& y = 1;
f(x); // error! cannot bind rvalue reference to lvalue
f(+x); // calls #4
f(static_cast<int&&>(x)); // calls #4
f(int(x)); // calls #4
f(const_cast<int const&&>(y)); // calls #5
f(const_cast<int&&>(y)); // calls #4
f(static_cast<int const&&>(y)); // calls #5
f(static_cast<int&&>(y)); // error! casts `const int` to `int&&`
f(std::move(y)); // calls #5
f(1); // calls #4
f(static_cast<int const&&>(1)); // calls #5
Case 8 (#1 and #4)
It is ambiguous even though the by-value candidate is clearly the better choice.
int x = 1;
int&& y = 1;
f(x); // calls #1
f(+x); // ambiguous
f(y); // calls #1
f(std::move(y)); // ambiguous
f(1); // ambiguous
f(static_cast<int>(1)); // ambiguous
f(static_cast<int&&>(1)); // ambiguous
f(const_cast<int const&&>(y)); // calls #1
f(std::move(x)); // ambiguous
static_cast<void(*)(int&&)>(f)(std::move(y)); // calls #4
static_cast<void(*)(int&&)>(f)(+x); // calls #4
static_cast<void(*)(int)>(f)(+x); // calls #1
static_cast<void(*)(int&&)>(f)(1); // calls #4
static_cast<void(*)(int)>(f)(1); // calls #1