3

I would like a clarification about this particular case:

class Test
{
    Test& operator=(const Test& copy)
    {
      ...
    }

    Test() = default;
}

Test a;
Test b;
b = a; //Is "a" converted to an rvalue?

"a" is an lvalue, however it is now the righthand operand of the assignment. Does it mean it gets converted to an rvalue?

This code doesn't compile:

class Test
{
    Test& operator=(Test&& copy) 
    {
      ...
    }

    Test() = default;
}

Test a;
Test b;
a = b; //Error

But this one does:

class Test
{
    Test& operator=(Test& copy) 
    {
      ...
    }

    Test() = default;
}

Test a;
Test b;
a = b; //OK

Since an lvalue reference cannot bind to an rvalue, does it mean a conversion is not happening here? If that is the case, when does an implicit lvalue-to-rvalue conversion happen? (Other than the case of the operator+ with primitive types)

yggdrasil
  • 737
  • 5
  • 14
  • 6
    `Test b = a;` is not an assignment, but an initialization. It doesn't use `operator=`, but a constructor. And no, there's no lvalue-to-rvalue conversion in your example. – Igor Tandetnik Apr 25 '18 at 12:36
  • Implicit lvalue-to-rvalue conversion happens whenever an lvalue is used in a context where rvalue is required. A copy constructor cannot be such a context - for a class type, lvalue-to-rvalue conversion implies copying, so if copy constructor required such a conversion, you'd have an infinite recursion. – Igor Tandetnik Apr 25 '18 at 12:40
  • @IgorTandetnik Sorry, you are right, I've corrected the question. This case applies to both assignments and initializations. – yggdrasil Apr 25 '18 at 12:41
  • 2
    You can [look at the AST](https://godbolt.org/g/QXzybf) to see how the compiler interprets this. It shows the lvalue going into the constructor directly. – wally Apr 25 '18 at 12:42
  • @wally Thank you, so no conversion is happening at all in this context. Could you give me a simple example where such a conversion with classes is required, causing a copy? – yggdrasil Apr 25 '18 at 12:46
  • Are you actually asking whether the right-hand argument to an assignment operator is always an rvalue _just because it's on the right_? Or something else? – Useless Apr 25 '18 at 12:47
  • @Useless Yes, that's the main point of the question. However as wally pointed out in this case there is no conversion so I guess the answer is "no". At the end I also ask in which case an implicit lvalue-to-rvalue conversion with non primitive types (classes) happens. An example code would be useful. – yggdrasil Apr 25 '18 at 12:52
  • There is an [extensive answer here](https://stackoverflow.com/a/20999389/1460794) dealing with lvalue to rvalue conversions. It specifically shows and explains the `x = y;` case. – wally Apr 25 '18 at 13:00
  • 2
    `void f(Test t) {} Test a; f(a);` The function call here would require an lvalue-to-rvalue conversion, since the function takes its parameter by value. – Igor Tandetnik Apr 25 '18 at 13:09
  • @IgorTandetnik I'm not sure why, but it does not seem to require the lvalue-to-rvalue conversion [here](https://godbolt.org/g/BAfows). I've posted a new question about this to understand better. – wally Apr 25 '18 at 14:27
  • @wally Print something in the copy-constructor. Yours is a no-op, and the compiler is smart enough to optimize the call away. – Igor Tandetnik Apr 25 '18 at 16:27

3 Answers3

2

This is lvalue, b = a; calls copy operator

class Test
{
    Test& operator=(const Test& copy)
    {
      ...
    }

    Test() = default;
}

Test a;
Test b;
b = a; //Is "a" converted to an rvalue?

Error, because your copy constructor and copy operator wasn't generated by compiler, because of declaring move operator

class Test
{
    Test& operator=(Test&& copy) 
    {
      ...
    }

    Test() = default;
}

Test a;
Test b;
a = b; //Error

If you want to get rvalue you can write this:

Test a;
Test b;
a = Test(); //Uses move operator
Alexey Usachov
  • 1,364
  • 2
  • 8
  • 15
2

There is no lvalue-to-rvalue conversion in your case (ignoring codes in the definiton of operator=).

Here you are using overloaded operators and according to [expr.pre]/2,

Overloaded operators obey the rules for syntax and evaluation order specified in [expr.compound], but the requirements of operand type and value category are replaced by the rules for function call.

So it does not require the right operand to be a prvalue, thus no lvalue-to-rvalue conversion.

xskxzr
  • 12,442
  • 12
  • 37
  • 77
1

"a" is an lvalue, however it is now the righthand operand of the assignment. Does it mean it gets converted to an rvalue?

In short, yes. In all versions and drafts of the C++ standard, one of the standard conversions is called the "lvalue-to-rvalue" conversion.

That is applied in the first case you describe.

Since an lvalue reference cannot bind to an rvalue, does it mean a conversion is not happening here? If that is the case, when does an implicit lvalue-to-rvalue conversion happen? (Other than the case of the operator+ with primitive types)

In the second case, you have declared/defined an operator=(Test &&) that accepts an rvalue reference, which suppresses generation of an operator=(const Test &). In C++11 and later, declaring a operator=(Test &&) without one of operator=(Test &) or operator(const Test &) prevents using an lvalue on the right hand side (as in your example, a = b).

Which does, indirectly, mean a lvalue-to-rvalue conversion is not employed in this case.

Peter
  • 35,646
  • 4
  • 32
  • 74
  • Thanks for the answer. One doubt thought. If conversion is happening in the first case, that should imply a copy in a temporary object for classes, which however doesn't seem to be happening. – yggdrasil Apr 25 '18 at 13:32
  • I have to admit, I don't understand this myself. [Here](https://godbolt.org/g/MYpQU6) I was able get clang to admit to an lvalue to rvalue conversion. But I could not see the conversion in the AST for any of the other examples. – wally Apr 25 '18 at 13:35
  • It CAN mean creating a temporary object - for example, if you pass `Test` to a function by value. If the conversion is producing a reference to a named object, however, it doesn't (necessarily) need to create a temporary. – Peter Apr 25 '18 at 13:39
  • @wally I find the standard to be very confusing about this implicit conversion. However I believe in the example Igor gave "Test t" is an lvalue too, so no conversion is needed (Just a copy). However in your example the return value of a function is a prvalue and so clang is doing the conversion (copy + conversion). – yggdrasil Apr 25 '18 at 13:54
  • @A.S. Ok that makes sense. [Here](https://stackoverflow.com/a/50025424/1460794) is a bit more clarification. – wally Apr 25 '18 at 15:50
  • Why would lvalue-to-rvalue happen when binding a reference parameter to the operand? – Davis Herring Sep 25 '19 at 14:03