Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to properly write R-Value overloads for operators

For context, the actual class I'm working with is considerably more complicated and larger than what I'm showing here, but I'm using this just as an example.

struct Vector {
    int x, y;
    Vector() : Vector(0,0) {}
    Vector(int x, int y) : x(x), y(y) {}
};

I'd like to add operator overloads to allow Vectors to be added and subtracted from each other.

Vector& operator+=(Vector const& v) {
    x += v.x;
    y += v.y;
    return *this;
}
Vector operator+(Vector const& v) const {
    return Vector(*this) += v;
}
Vector& operator-=(Vector const& v) {
    x -= v.x;
    y -= v.y;
    return *this;
}
Vector operator-(Vector const& v) const {
    return Vector(*this) -= v;
}

However, This code can allow unfortunate constructions:

int main() {
    Vector & a = Vector(1,2) += Vector(5,4);//This compiles and invokes undefined behavior!
    std::cout << a.x << ',' << a.y << std::endl;//This isn't safe!
}

So I rewrote the code to be mindful of whether the object is an L-value or R-value:

Vector& operator+=(Vector const& v) & {
    x += v.x;
    y += v.y;
    return *this;
}
Vector&& operator+=(Vector const& v) && {
    return std::move(*this += v);
}
Vector operator+(Vector const& v) const {
    return Vector(*this) += v;
}
Vector& operator-=(Vector const& v) & {
    x -= v.x;
    y -= v.y;
    return *this;
}
Vector&& operator-=(Vector const& v) && {
    return std::move(*this -= v);
}
Vector operator-(Vector const& v) const {
    return Vector(*this) -= v;
}

So my remaining question is, even though this code compiles and does what I'm expecting, is this code safe and free of unexpected Undefined Behavior?

int main() {
    //No Longer compiles, good.
    //Vector & a = Vector(1,2) += Vector(5,4);

    //Is this safe?
    Vector b = Vector(1,2) += Vector(5,4);

    //Other cases where this code could be unsafe?
}
like image 789
Xirema Avatar asked Jan 05 '18 17:01

Xirema


People also ask

How to declare overloaded operators?

An overloaded operator (except for the function call operator) cannot have default arguments or an ellipsis in the argument list. You must declare the overloaded = , [] , () , and -> operators as nonstatic member functions to ensure that they receive lvalues as their first operands.

Can assignment operator be overloaded?

You can overload the assignment operator (=) just as you can other operators and it can be used to create an object just like the copy constructor.

Which of the following C++ operators we can overload?

Explanation: Both arithmetic and non-arithmetic operators can be overloaded. The precedence and associativity of operators remains the same after and before operator overloading.

What is function and operator overloading?

C++ allows you to specify more than one definition for a function name or an operator in the same scope, which is called function overloading and operator overloading respectively.


2 Answers

Here are relatively standard ways to overload these operators:

Vector& operator+=(Vector const& v)& {
  x += v.x;
  y += v.y;
  return *this;
}
friend Vector operator+(Vector lhs, Vector const& v) {
  lhs+=v;
  return std::move(lhs); // move is redundant yet harmless in this case
}
Vector& operator-=(Vector const& v)& {
  x -= v.x;
  y -= v.y;
  return *this;
}
friend Vector operator-(Vector lhs, Vector const& v) {
  lhs -= v;
  return std::move(lhs); // move is redundant yet harmless in this case
}

note that on a line of many + or -, the above generates way fewer copies and more moves than your overloads do.

a+b+c becomes (a+b)+c, and the return value of a+b is elided directly into the lhs argument of +c. And the first line of your + consisted of creating a copy anyhow, so the extra copy in the signature is harmless.

Unless you have a great reason why, ban += and = on rvalues. int doesn't support it, you shouldn't either.

like image 181
Yakk - Adam Nevraumont Avatar answered Oct 10 '22 17:10

Yakk - Adam Nevraumont


When in doubt, do as int do.

Can you do compound assignment on int rvalues? Of course not. So why bother with your Vector?


Your b is safe, but Vector&& c = Vector(1,2) += Vector(5,4); isn't. The usual fix is to return by value, but returning by value from an assignment operator is also somewhat weird.

like image 29
T.C. Avatar answered Oct 10 '22 18:10

T.C.