Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to avoid overloaded assignment operator turning rvalue to lvalue?

Tags:

c++

c++11

To my knowledge, returning *this is the norm of writing overloaded operator=. But this may promote a rvalue to a lvalue!

struct Foo {
  Foo& operator=(const Foo &t) { return *this; }
};

int main() {
  const Foo &ref = Foo();
  //Foo &ref1 = Foo();  // This line won't compile.
  //But the following line compiles. Isn't it dangerous?
  //Also please note that if = is not overloaded, the synthesized operator= will NOT let it compile.
  Foo &ref2 = (Foo() = Foo());

  return 0;
}
like image 559
updogliu Avatar asked Aug 19 '12 01:08

updogliu


2 Answers

You're right. The short answer is that this is legal, but is dangerous for the reasons you point out.

Another (similar) example was added to C++11, as part of the standard no less, with the rvalue stream output, which returns an lvalue reference.

template <class charT, class traits, class T>
basic_ostream<charT, traits>&
operator<<(basic_ostream<charT, traits>&& os, const T& x);

So std::ostream& s = std::ofstream("foo.txt") << "Oh boy"; is legal, but creates a dangling reference.

I believe that C++11 compilers will allow member functions to be restricted to the case when the object is an lvalue, which should allow you to prevent the problem at all. The below will only allow the assignment when *this is an lvalue, so it prevents you from converting by accident.

struct Foo {
  Foo& operator=(const Foo &t) & { return *this; }
};
like image 195
Dave S Avatar answered Nov 20 '22 15:11

Dave S


As the first answer points out, what you're doing is legal but will result in a dangling reference. You can achieve much the same result without overloading the assignment operator:

#include <iostream>

struct Foo {
  Foo(int arg) : val(arg) { std::cout << "ctor " << val << "\n"; }
  ~Foo() { std::cout << "dtor " << val << "\n"; }
  int val;
};

int main() {
  Foo &ref = (Foo(1) = Foo(2));
  std::cout << "undefined behavior: " << ref.val << "\n";

  return 0;
}

Produces the following output:

ctor 1
ctor 2
dtor 2
dtor 2
undefined behavior: 2

As you can see, the temporary for Foo(1) is constructed followed by the temporary for Foo(2). However both objects are destroyed before we can do anything with the reference, so the subsequent line results in undefined behavior.

like image 38
Benjamin Kay Avatar answered Nov 20 '22 15:11

Benjamin Kay