Move Semantics and Operator Return Type

Let's look at an example from Exceptional C++ Item 20: Class Mechanics.

class Complex
{
public:
    explicit Complex(double real, double imaginary = 0) :
                real_(real), imaginary_(imaginary) {}

    Complex& operator+=(const Complex& other) {
        real_+=other.real_;
        imaginary_+= other.imaginary_;
        return *this;
    }

private:
    double real_;
    double imaginary_;
};

const Complex operator+(const Complex& lhs, const Complex& rhs) {
    Complex ret(lhs);
    ret += rhs;
    return ret;
}

Why return a const object?

The return type of the temporary from the overloaded operator+ is const Complex. The intention here, as written in the book, is to prevent usage like (A+B)=C.

However, at the time of writing, move semantics haven't been introduced yet. If we take move semantics into account, the const Complex return type may unexpectedly disable move assignment.

// Example program
#include <iostream>
#include <string>

class Complex
{
public:
    explicit Complex(double real, double imaginary = 0) :
                real_(real), imaginary_(imaginary) {}

    Complex (const Complex& other) {
        real_ = other.real_;
        imaginary_ = other.imaginary_;
    };

    Complex& operator+=(const Complex& other) {
        real_+=other.real_;
        imaginary_+= other.imaginary_;
        return *this;
    }

    Complex& operator=(const Complex& other)
    {
         real_ = other.real_;
         imaginary_ = other.imaginary_;
         std::cout << "copy assigned\n";
         return *this;
    }

    Complex& operator=(Complex&& other)
    {
         real_ = std::move(other.real_);
         imaginary_ = std::move(other.imaginary_);
         std::cout << "move assigned\n";
         return *this;
    }

private:
    double real_;
    double imaginary_;
};

const Complex operator+(const Complex& lhs, const Complex& rhs) {
    Complex ret(lhs);
    ret += rhs;
    return ret;
}

int main()
{
  Complex A(1, 2);
  Complex B(3, 4);
  Complex C(5, 6);

  C = A + B; // copy assigned

  return 0;
}

The parameter types of copy assignment and move assignment operators are const Complex& and Complex&& respectively. Note the return type of the expression A + B is const Complex. When we assign this temporary to C, the copy assignment operator is preferred, instead of calling move assignment as we expected.

Why move assignment is not selected?

The trick is in functions overload resolution, both copy and move assignment operators require a conversion from the passed argument const Complex. Here, a lvalue-to-rvalue conversion for copy assignment operator is preferred over a qualification conversion (discarding the const qualifier), so copy assignment is preferred.

An expression like A + B = C should be relatively easy to avoid, but the unexpected call of copy assignment instead of move assignment may be harder to notice. My suggestion here is to return a non-const object from overloaded operators.