1

Consider the following example struct:

struct A {
    int i;

    void init(const A &a)
    {
        this->i = a.i;
    }

    A(int i)
    {
        this->i = i;
    }

    A(const A &a) = delete;
    A &operator=(const A &a) = delete;

    A(A &&a)
    {
        init(a); // Is this allowed?
    }

    A &operator=(A &&a)
    {
        init(a); // Is this allowed?
        return *this;
    }
};

The rvalue reference A &&a is passed on to a function accepting a const A &a, i.e. a constant lvalue reference. Is this allowed and resulting in well defined behaviour in C++?

HerpDerpington
  • 3,751
  • 4
  • 27
  • 43
  • 1
    The name of a variable whose type is "rvalue reference" is an lvalue, and can be used like every other lvalue. – cpplearner Oct 08 '19 at 19:46
  • @cpplearner So that would imply that this is valid? I saw an comment here a second ago that said "no". – HerpDerpington Oct 08 '19 at 19:51
  • Possible duplicate of [assigning Rvalue reference to Lvalue reference](https://stackoverflow.com/questions/37894068/assigning-rvalue-reference-to-lvalue-reference) – walnut Oct 08 '19 at 19:55
  • 1
    alternative duplicate: https://stackoverflow.com/questions/26369511/why-can-you-indirectly-bind-an-rvalue-to-an-lvalue-reference-but-not-directly – walnut Oct 08 '19 at 19:56
  • @uneven_mark While the questions are similar, the answers don't give a definite "yes or no" on whether this is intended to work the way that it does. – HerpDerpington Oct 08 '19 at 20:02
  • 1
    @HerpDerpington I am not sure what you mean with "*is intended to work the way it does*". As explained in the duplicates your code is syntactically correct and semantically I assumed it was clear that the bound reference behaves the same way as all other references from the fact that the duplicates' answers didn't mention anything else. – walnut Oct 08 '19 at 20:09

2 Answers2

1

Yes, it is allowed.

Note, that value category of the expression a is lvalue, even though a's declared type is rvalue reference.

Furthermore, if you create an rvalue from a with std::move, your code is still well-formed, as an rvalue can be bound to a const lvalue reference:

init(std::move(a)); // std::move(a) is an rvalue (to be precise, xvalue), but still OK
geza
  • 28,403
  • 6
  • 61
  • 135
1

You need to cast a to an rvalue in your example to get the intended effect, because variable names themselves are lvalues (this is a tricky detail of lvalues and rvalues in C++). So as written, it's correct C++ but not doing what you think it's doing.

If you cast it, using std::move(a) instead of just a, the code now does what you want and is still correct. This is because of a special rule in C++ that temporaries can be bound to const lvalues, a more detailed discussion of which can be found here. This features comes as a great convenience when you have code like:

void ProcessData(const std::vector<int>& input_vector);

and then you want to test it with the following:

ProcessData(std::vector<int>{1, 2, 3, 4, 5});

This saves you from having to explicitly create the object before passing it on as a const lvalue reference. Note the const here is critical, without it the code is incorrect. There is a more detailed discussion of this rationale for this choice here.

Apollys supports Monica
  • 2,938
  • 1
  • 23
  • 33
  • 2
    What does using `std::move(a)` instead of `a` change here? Both will still call `init(const A &a)`, since there is no overload `init(A &&a)`. – walnut Oct 08 '19 at 20:31
  • What I want is simply that `i` gets assigned the same value as it has in the instance passed as rvalue to the move constructor/operator. – HerpDerpington Oct 08 '19 at 20:35
  • 1
    @HerpDerpington Yes it does (assuming of course the object bound to the initial reference is in its lifetime, but that requirement applies always anyway). – walnut Oct 08 '19 at 21:32
  • 1
    Good question @uneven_mark, I glazed over that a little. Using `std::move` here causes you to actually pass an rvalue to the function `init`, without `std::move` you are passing an lvalue. So the important point is that without it, the code is not doing what the OP is asking about, i.e. is passing an rvalue into a parameter that's expected to be a const lvalue reference. At the same time, in both cases the code will work and do the same thing, in fact I wouldn't be surprised if some compilers produce the same code for both (unless I've missed a tricky subtlety). – Apollys supports Monica Oct 08 '19 at 22:11
  • @Apollys By "passing an rvalue" i actually meant passing `A &&a` as in the move constructors parameter list. – HerpDerpington Oct 08 '19 at 22:19
  • 1
    *"but not doing what you think it's doing"* makes it sound as if the resulting programs behavior would be different. To consider the expression put as argument being a lvalue rather than rvalue as *"not doing what you think it's doing"* sounds a bit far-fetched to me (the potential overloading issue which doesn't apply here aside). And in fact I don't see OP claiming anywhere that they are passing a rvalue, only that they are passing a rvalue reference, which is correct. – walnut Oct 08 '19 at 22:22
  • @HerpDerpington In the line `init(a)` that you have, `a` is an lvalue. So if your question is, does your code work exactly as written, then yes (almost trivially so once you're given the previous statement), because you're passing an lvalue to a function that takes an lvalue reference. – Apollys supports Monica Oct 08 '19 at 22:51
  • @Apollys But then my question is why is it an lvalue if the variable was declared as `A &&a`? Or rather I can see that it is. Nevertheless, using it as an lvalue seems counterintuitive and the question would be whether this is allowed. – HerpDerpington Oct 08 '19 at 23:00
  • 1
    Well that's one of those tricky details (as I mentioned in my answer). [Here](https://stackoverflow.com/questions/32620750/why-are-rvalues-references-variables-not-rvalue) is a good discussion of it. – Apollys supports Monica Oct 08 '19 at 23:04
  • @Apollys From the top answer of that post: "`T&&` is the type rvalue reference. An rvalue reference can only bind to an rvalue (without a `static_cast` being involved), but it is otherwise an lvalue of type rvalue reference." If the type is "rvalue reference" why does it cast/is it being casted to a "regular" lvalue reference? – HerpDerpington Oct 08 '19 at 23:19
  • 1
    Try the second answer, I found it explains some elements more clearly. The distinction between lvalues and rvalues is quite complicated and I don't think I can do it justice in a comment. – Apollys supports Monica Oct 08 '19 at 23:29