Normal OR Rules for Reference Bindings

教育   2024-05-19 16:53   美国  

这不是一篇文章,原是图表分享形式,后因代码过多,转换成了文本,故仅有少数说明文字。

What are the ranking rules for reference bindings?
Let's consider the following example:
1void f(int);         // #1
2void f(int&);        // #2
3void f(int const&);  // #3
4void f(int&&);       // #4
5void f(int const&&)// #5
Here are some key points:
  1. #1 is a function that accepts borrowed int arguments.

  2. #2 is a function that takes an lvalue reference.

  3. #3 is a function that takes a const lvalue reference, which accepts both lvalue and xvalue arguments.

  4. #4 is a function that takes an rvalue reference.

  5. #5 is a function that takes a const rvalue reference, which accepts an xvalue with a const qualifier.

  6. All references bind to lvalues/xvalues.

  7. Numeric literals are non-const.

Case 1 (#1 and #2)

1int x = 1;
2f(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:
1f(+x);                            // calls #1 by using the unary plus
2f(int(x));                        // calls #1 by explicitly copying the object
3f(const_cast<int const&>(x));     // calls #1 by using const_cast
4f(std::cref(x));                  // calls #1 by using std::cref
5f(static_cast<int&&>(x));         // calls #1 by casting the argument type
6static_cast<void(*)(int)>(f)(x);  // calls #1 by casting the function type
7static_cast<void(*)(int&)>(f)(x); // calls #2 by casting the function type

Case 2 (#1, #2, and #3)

1int x = 1;
2f(x); // ambiguous!
To avoid ambiguity, we must cast the function type(best avoided):
1static_cast<void(*)(int)>(f)(x);  // calls #1 by casting the function type
2static_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.
1int x = 1;
2int const& y = x;
3f(x);                         // calls #2
4f(+x);                        // calls #3
5f(const_cast<int const&>(x)); // calls #3
6f(1);                         // calls #3
7f(y);                         // calls #3

Case 4 (#1 and #3)

There is absolutely no way to distinguish between #1 and #3 for the compiler.
1int x = 1;
2int const& y = x;
3f(x);                                   // ambiguous
4f(y);                                   // ambiguous
5f(const_cast<int const&>(x));           // ambiguous
6f(+x);                                  // ambiguous
7f(static_cast<int&&>(x));               // ambiguous
8f(1);                                   // ambiguous
9static_cast<void(*)(int)>(f)(x);        // calls #1
10static_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.
1f(x);                                    // calls #3
2f(+x);                                   // calls #5
3f(static_cast<int&&>(x));                // calls #5
4f(const_cast<int const&>(x));            // calls #3
5f(int(x));                               // calls #5
6static_cast<void(*)(int const&)>(f)(x);  // calls #3
7static_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.
1int x = 1;
2int const&& y = 1;
3f(x);                           // error! cannot bind rvalue reference to lvalue
4f(+x);                          // calls #4
5f(static_cast<int&&>(x));       // calls #4
6f(int(x));                      // calls #4
7f(const_cast<int const&&>(y));  // calls #5
8f(const_cast<int&&>(y));        // calls #4
9f(static_cast<int const&&>(y)); // calls #5
10f(static_cast<int&&>(y));       // error! casts `const int` to `int&&`
11f(std::move(y));                // calls #5
12f(1);                           // calls #4
13f(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.
1int x = 1;
2int&& y = 1;
3f(x);                                         // calls #1
4f(+x);                                        // ambiguous
5f(y);                                         // calls #1
6f(std::move(y));                              // ambiguous
7f(1);                                         // ambiguous
8f(static_cast<int>(1));                       // ambiguous
9f(static_cast<int&&>(1));                     // ambiguous
10f(const_cast<int const&&>(y));                // calls #1
11f(std::move(x));                              // ambiguous
12static_cast<void(*)(int&&)>(f)(std::move(y)); // calls #4
13static_cast<void(*)(int&&)>(f)(+x);           // calls #4
14static_cast<void(*)(int)>(f)(+x);             // calls #1
15static_cast<void(*)(int&&)>(f)(1);            // calls #4
16static_cast<void(*)(int)>(f)(1);              // calls #1
Got any questions? Just drop a comment below!



CppMore
Dive deep into the C++ core, and discover more!
 最新文章