Overload Resolution Between Object, Rvalue Reference, Const Reference

Overload resolution between object, rvalue reference, const reference

What are the rules here?

As there is only one parameter, the rule is that one of the three viable parameter initializations of that parameter must be a better match than both the other two. When two initializations are compared, either one is better than the other, or neither is better (they are indistinguishable).

Without special rules about direct reference binding, all three initializations mentioned would be indistinguishable (in all three comparisons).

The special rules about direct reference binding make int&& better than const int&, but neither is better or worse than int. Therefore there is no best match:

S1    S2
int int&& indistinguishable
int const int& indistinguishable
int&& const int& S1 better

int&& is better than const int& because of 13.3.3.2:

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.

But this rule does not apply when one of the initializations is not a reference binding.

Is there any chance int && may be preferred over int in a future standard? The reference must bind to an initializer, whereas the object type is not so constrained. So overloading between T and T && could effectively mean "use the existing object if I've been given ownership, otherwise make a copy."

You propose to make a reference binding a better match than a non-reference binding. Why not post your idea to isocpp future proposals. SO is not the best for subjective discussion / opinion.

Write overloads for const reference and rvalue reference

My opinion is that understanding (truly) how std::move and std::forward work, together with what their similarities and their differences are is the key point to solve your doubts, so I suggest that you read my answer to What's the difference between std::move and std::forward, where I give a very good explanation of the two.


In

void foo(MyObject &&obj) {
globalVec.push_back(obj); // Moves (no, it doesn't!)
}

there's no move. obj is the name of a variable, and the overload of push_back which will be called is not the one which will steal reasources out of its argument.

You would have to write

void foo(MyObject&& obj) {
globalVec.push_back(std::move(obj)); // Moves
}

if you want to make the move possible, because std::move(obj) says look, I know this obj here is a local variable, but I guarantee you that I don't need it later, so you can treat it as a temporary: steal its guts if you need.

As regards the code duplication you see in

void foo(const MyObject &obj) {
globalVec.push_back(obj); // Makes copy
}
void foo(MyObject&& /*rvalue reference -> std::move it */ obj) {
globalVec.push_back(std::move(obj)); // Moves (corrected)
}

what allows you to avoid it is std::forward, which you would use like this:

template<typename T>
void foo(T&& /* universal/forwarding reference -> std::forward it */ obj) {
globalVec.push_back(std::forward<T>(obj)); // moves conditionally
}

As regards the error messages of templates, be aware that there are ways to make things easier. for instance, you could use static_asserts at the beginning of the function to enfornce that T is a specific type. That would certainly make the errors more understandable. For instance:

#include <type_traits>
#include <vector>
std::vector<int> globalVec{1,2,3};

template<typename T>
void foo(T&& obj) {
static_assert(std::is_same_v<int, std::decay_t<T>>,
"\n\n*****\nNot an int, aaarg\n*****\n\n");
globalVec.push_back(std::forward<T>(obj));
}

int main() {
int x;
foo(x);
foo(3);
foo('c'); // errors at compile time with nice message
}

Then there's SFINAE, which is harder and I guess beyond the scope of this question and answer.

My suggestion

Don't be scared of templates and SFINAE! They do pay off :)

There's a beautiful library that leverages template metaprogramming and SFINAE heavily and successfully, but this is really off-topic :D

Why rvalue reference argument matches to const reference in overload resolution?

This behaviour is attributed to overload resolution rules. As per standard 8.5.3/p5.2 References [dcl.init.ref], rvalue references bind to const lvalue references. In this example:

std::data(T())

You provide to std::data an rvalue. Thus, due to overload resolution rules the overload:

template <class _Cont> constexpr
auto data(const _Cont& __c) -> decltype(__c.data()) { return __c.data(); }

is a better match. Consequently you get const int*

You can't bind a temporary to a non-const lvalue reference.

Why does this overload resolution select the signature with the rvalue reference?

In your example code, U is a forwarding reference and not an RValue reference:

template<typename T, typename U>
static void append_list(T &t, U &&u) {
// ^~~~~

Forwarding references behave differently than the usual template deduced types in that U will become the exact type of its input, matching both the CV-qualifiers and value category.

  • For PR values of type T, this produces U = T
  • For X-values of type T, this produces U = T&&
  • For L-values of type T, this produces U = T&

This differs from normal template matching where a deduced type from const U& would determine U = T.

When presented as an overload set with a function template that deduces its arguments via template matching, forwarding references will effectively be "greedy" -- since in most cases it will be an unambiguously better match for overload resolution.


With your example code:

int main() {
auto shmoo = std::make_shared<foo>();
std::vector<int> baz{2};
append_list(baz, shmoo->bar);
}

schmoo->bar is passing a non-const lvalue reference of std::vector<int> into append_list.

During overload resolution, the compiler will always resolve the function with the most exact match (i.e. requires least number of conversions required). In the above overload, std::vector<int>& could be matched to const U& = const std::vector<T>& -- but this requires adding const, compared to matching U&& = std::vector&` which is an exact match.

As a result, the forward-reference overload is called.

const/non-const rvalue reference in overload resolution

The type of (const int)1 is adjusted to int before overload resolution.

[expr]/6:

If a prvalue initially has the type “cv T”, where T is a cv-unqualified non-class, non-array type, the type of the expression is adjusted to T prior to any further analysis.

Why const lvalue reference has priority over const rvalue reference during overloading resolution

tl;dr: It was re-designed that way.

Under the original move proposal your code would be ambiguous. Under that proposal, lvalues could bind to rvalue references, but would prefer an lvalue reference if it existed in the overload set.

Fairly late in the process, as more people began to understand the proposal, and as concepts were still being considered for C++11, the rules were changed so that lvalues could not bind to rvalue references.

I personally did not feel this change was necessary, but far more people liked the change than not liked it, and the basic functionality of move semantics would work either way. So this was definitely a compromise worth making as opposed to not getting move semantics at all.

With the change that lvalues can not bind to rvalue references, A(B const &&, C const &&) is not part of the overload resolution set if either argument is an lvalue. However A(B const &, C const &) remains in the overload set if either (or both) arguments are lvalues.

Overload resolution between const lvalue reference and rvalue reference

First the compiler performs an implicit array-to-pointer conversion for "abc", so the type of "abc" becomes const char*. Second (and you probably missed that), const char* is converted to a rvalue std::string via the const char* non-explicit constructor of std::string (# 5 in the link). The constructed std::string rvalue is a perfect match for the second overload, so the second overload is chosen.



Related Topics



Leave a reply



Submit