4

The most popular operator which should return reference is operator=

class Alpha
{
    int x;
    int y;
    std::string z;
  public:
    void print()
        { cout << "x " << x << " y " << y << "  " << z << '\n'; }
    Alpha(int xx=0, int yy=0, std::string zz=""): x(xx), y(yy), z(zz) {}
    Alpha operator=(Alpha& another)
    {
        x = another.x;
        y = another.y;
        z = another.z;
        return *this;
    }


};

int main()
{
    Alpha a(3,4,"abc"),b,c;
    b=c=a;
    return 0;
}

Clang says this:

clang++-3.6 new.cxx -o new new.cxx:70:3: error: no viable overloaded '=' b=c=a; ~^~~~ new.cxx:34:8: note: candidate function not viable: expects an l-value for 1st argument Alpha operator=(Alpha& another) ^ 1 error generated.

gcc this:

new.cxx:34:8: note: no known conversion for argument 1 from ‘Alpha’ to ‘Alpha&’

But I can't understand what's the problem in theory. What I think happens:

  1. Firstly operator= is called for object c. It receives object a by reference, copies it's values to c and returns anonymous copy of it's self (of object c): Copy soncstructor is called.
  2. Then operator= is called for object b. It requires rvalue ref, but we have only written lvalue reference, so the error occurs.

I have added rval operator= and copy constructor, which receives lvalue reference and everything works, now I have no idea why (I should have written rvalue copy constructor which receive const Alpha& s or Alpha&& s):

class Alpha
{
    int x;
    int y;
    std::string z;
  public:
    void print()
    { cout << "x " << x << " y " << y << "  " << z << '\n'; }
    Alpha(int xx=0, int yy=0, std::string zz=""): x(xx), y(yy), z(zz) {}
    //Alpha(Alpha&& s): x(s.x), y(s.y), z(s.z) {}
    Alpha(Alpha&& ) = delete;
    Alpha(Alpha& s): x(s.x), y(s.y), z(s.z) {}
    Alpha operator=(Alpha& another)
    {
        x = another.x;
        y = another.y;
        z = another.z;
        return *this;
    }
    Alpha operator=(Alpha&& another)
    {
        x = another.x;
        y = another.y;
        z = another.z;
        return *this;
    }

};
6
  • Why the superfluous copy at the end? That's why you should return a reference. Commented Feb 21, 2016 at 10:12
  • 1
    I suggest you add print statements to all relevant functions, and see exactly how long a chain call you invoke in each case. The compiler can't always optimize it all out. Commented Feb 21, 2016 at 10:14
  • What is your question exactly? The text in the question body seems to have nothing to do with the question in the title.
    – M.M
    Commented Feb 21, 2016 at 10:22
  • 1
    @yanpas well the answer is "it doesn't" . You can return by value. But returning by reference is more efficient.
    – M.M
    Commented Feb 21, 2016 at 10:34
  • 1
    What's the problem of returning by reference? And please don't say "class" when you mean "object", it's confusing as hell. Commented Feb 21, 2016 at 10:46

2 Answers 2

9

This signature for an assignment operator

Alpha operator=(Alpha& another)

is unusual in two ways. The first is that it returns a copy of the assigned-to object. Very rare to do that. The other is that it accepts a non-const reference as a parameter.

The non-const reference makes it not accept temporary objects as a parameter (as those will only bind to a const lvalue reference).

In combination this means that the temporary returned from the first operator= can not be used as a parameter to the second operator=.

Your options are to either return a reference, or to make the parameter Alpha const&. Both options would work individually, or in combination.

The third option, as you found out, is to explicitly add a move assignment operator, using Alpha&& that specifically accepts temporaries.

The standard way though is to declare the copy assignment operator

Alpha& operator=(Alpha const& other);

unless you have very specific reasons for selecting another signature.

1
  • So returning reference in this case is only good programming style, not necesarry? The same can't be applied for overloading ostream e. g. (ostream& operatror<<(ostream& s, const Alpha& A);), here ref returning is necessary
    – yanpas
    Commented Feb 21, 2016 at 12:26
1

This behaviour is made on purpose, to give us opportunity to fine tune code:

  • In the first snippet, you have explicitely defined operator=() to require an lvalue reference. As in your chain you don't provide such a reference, the compiler complains.

  • In the second snippet, you have added an overload of operator=() that accepts an rvalue reference. So this can handle the return by value without breaking the chain.

In your specific case, both alternatives have exactly the same code and this is why you don't understand the problem. But for more complex data structures, it could perfectly make sense to have a different behaviour. Imagine that you would have in your class a vector member of thousands of elements:

class Alpha {
    vector<double> hugevec; 
    ...
};

In the case of a reference assignment you'd do as usual (because original object you have to be kept alive):

Alpha operator=(Alpha& another)
{
    ...
    hugevec = another.hugevec;  // thousands of elements get duplicated
    return *this;
}

But in case of an assignment from a temporary value object, you could "steal" the exising vector because it will be discarded anyway (you could also reuse one of its pointer members, instead of allocating a new object an copying it, and destroying the old one):

Alpha operator=(Alpha&& another)
{
    ...
    swap (another.hugevec);  // no elements get duplicated !
                             // the old elements are now in the temporary 
                             // which will take care of their destruction
    return *this;
}

So this subtle distinction made by the compiler enables significant performance improvements.

By the way, this shall not be confused with the universal reference that use the same && syntax but in templates, and which let the compiler choose which of rvalue or lvalue reference is most appropiate.

Edit: The following articles may be of interest to you:

2
  • Alpha operator=(Alpha&& another) is move assignment operator. Since C++11 Alpha operator=(Alpha&& another) and Alpha operator=(const Alpha& another) are both allowed in the same class
    – yanpas
    Commented Feb 21, 2016 at 15:44
  • @yanpas yes and if both are present, for temporary objects the compiler will pick the move one. So in the chain b=c=a of OP, as he has defined assignment operator returning by value instead of by reference c=a will use Alpha operator=(const Alpha&) and b=... will use Alpha operator=(const Alpha&&)
    – Christophe
    Commented Feb 21, 2016 at 16:09

Not the answer you're looking for? Browse other questions tagged or ask your own question.