Operator overload which permits capturing with rvalue but not assigning to
Have the assignment operator be callable on lvalues only:
class C
{
// ...
public:
C& operator=(const C&) & = default;
};
Note the single ampersand after the closing parenthesis. It prevents assigning to rvalues.
Prevent assigning to rvalue
If you don't want your operator=()
to be invoked on temporaries (rvalues), then qualify it lvalue as:
Test& operator =(const Test&) & {return *this;}
//^^^ note this
Now the following
f() = t;
will give compilation error.
C++ Is a special rvalue-variant of an overloaded operator necessary?
This signature:
inline X operator+(X lhs, const X& rhs)
allows both rvalues and lvalues as the left-hand side of the operation. lvalues would just be copied into lhs
, xvalues would be moved into lhs
, and prvalues would be initialized directly into lhs
.
The difference between taking lhs
by value and taking lhs
by const&
materializes when we chain multiple +
operations. Let's just make a table:
+====================+==============+=================+
| | X const& lhs | X lhs |
+--------------------+--------------+-----------------+
| X sum = a+b; | 1 copy | 1 copy, 1 move |
| X sum = X{}+b; | 1 copy | 1 move |
| X sum = a+b+c; | 2 copies | 1 copy, 2 moves |
| X sum = X{}+b+c; | 2 copies | 2 moves |
| X sum = a+b+c+d; | 3 copies | 1 copy, 3 moves |
| X sum = X{}+b+c+d; | 3 copies | 3 moves |
+====================+==============+=================+
Taking the first argument by const&
scales in the number of copies. Each operation is one copy. Taking the first argument by value scales in the number of copies. Each operation is just one move but for the first argument being a lvalue means an additional copy (or an additional move for xvalues).
If your type isn't cheap to move - for those cases where moving and copying are equivalent - then you want to take the first argument by const&
since it is at least as good as the other case and there's no reason to fuss.
But if it's cheaper to move, you actually probably want both overloads:
X operator+(X const& lhs, X const& rhs) {
X tmp(lhs);
tmp += rhs;
return tmp;
}
X operator+(X&& lhs, X const& rhs) {
lhs += rhs;
return std::move(lhs);
}
This will use moves instead of copies for all the intermediate temporary objects, but will save you one move on the first one. Unfortunately, the best solution is also the most verbose.
Why does this C++ snippet assigning a value to an rvalue compile?
a*b = c;
calls the assignment operator on the Rational
returned by a * b
. The assignment operator generated is the same as if the following were defined:
Rational& Rational::operator=(const Rational&) = default;
There is no reason why this shouldn't be callable on a temporary Rational
. You can also do:
Rational{1,2} = c;
If you wanted to force the assignment operator to be callable only on lvalues, you could declare it like this, with a &
qualifier at the end:
Rational& operator=(const Rational&) &;
Why can I use assignment operator on begin() even if it is an rvalue?
The reason is historical. In the initial days of the language, there was simply no way for user code to express that a type's copy-assignment operator should only work on l-values. This was only true for user-defined types of course; for in-built types assignment to an r-value has always been prohibited.
int{} = 42; // error
Consequently, for all types in the standard library, copy-assignment just "works" on r-values. I don't believe this ever does anything useful, so it's almost certainly a bug if you write this, but it does compile.
std::string{} = "hello"s; // ok, oops
The same is true for the iterator type returned from v.begin()
.
From C++11, the ability to express this was added in the language. So now one can write a more sensible type like this:
struct S
{
S& operator=(S const &) && = delete;
// ... etc
};
and now assignment to r-values is prohibited.
S{} = S{}; // error, as it should be
One could argue that all standard library types should be updated to do the sensible thing. This might require a fair amount of rewording, as well as break existing code, so this might not be changed.
Are temporary C++ objects lvalues?
Objects are not lvalues or rvalues. The words lvalue and rvalue are expression categories. They categorize expressions, not objects.
For the line:
returnsA() = anotherA;
since the operands of =
are class types, an overloaded operator function is searched for. operator=
is special in that if the user does not explicitly overload it, there is a default overload generated. That overload is a member function with signature:
A& A::operator=(A const&);
and the call returnsA() = anotherA;
is transformed to returnsA().operator=(anotherA);
, this is the basic mechanics of member operator overloading.
The expression returnsA()
has category prvalue. However it is perfectly fine to call member functions on prvalue expressions, so there is no problem here.
If you want to dissuade your class from accepting this sort of assignment, see here. And for why the default assignment operator doesn't work that way, see here.
so what is the type of this ? Why is this not a lvalue?
For some class X, this
has the type X* this;
, but you're not allowed to assign to it, so even though it doesn't actually have the type X *const this
, it acts almost like it was as far as preventing assignment goes. Officially, it's a prvalue
, which is the same category as something like an integer literal, so trying to assign to it is roughly equivalent to trying to assign a different value to 'a'
or 10
.
Note that in early C++, this
was an lvalue -- assigning to this
was allowed -- you did that to handle the memory allocation for an object, vaguely similar to overloading new
and delete
for the class (which wasn't supported yet at that time).
Related Topics
Does a C/C++ Compiler Optimize Constant Divisions by Power-Of-Two Value into Shifts
Changing the Directory from Inside a C Program Under Windows Using System Command
Are Class Members Guaranteed to Be Contiguous in Memory
Is a Logical Right Shift by a Power of 2 Faster in Avr
What Happens When You Bit Shift Beyond the End of a Variable
How to Compare Two Time Stamp in Format "Month Date Hh:Mm:Ss" to Check +Ve or -Ve Value
Why I Can Access Member Functions Even After the Object Was Deleted
When Instantiating a Template, Should Members of Its Incomplete Argument Types Be Visible
Operator Overload Which Permits Capturing with Rvalue But Not Assigning To
Undefined Behavior Causing Time Travel
Is There an Intra-Process Local Pipe in Qt
How to Avoid Including Class Implementation Files
Why Is Locking a Std::Mutex Twice 'Undefined Behaviour'
What Does "#Define Str(A) #A" Do
Convert String with Explicit Escape Sequence into Relative Character
Serializing Opencv Mat_<Vec3F>
Wrote to a File Using Std::Wofstream. the File Remained Empty