Reference-type conversion operators: asking for trouble?

Ben picture Ben · Jun 24, 2009 · Viewed 10.8k times · Source

When I compile the following code using g++

class A {};

void foo(A&) {}

int main()
{
  foo(A());
  return 0;
}

I get the following error messages:

> g++ test.cpp -o test     
test.cpp: In function ‘int main()’:
test.cpp:10: error: invalid initialization of non-const reference of type ‘A&’ from a temporary of type ‘A’
test.cpp:6: error: in passing argument 1 of ‘void foo(A&)’

After some reflection, these errors make plenty of sense to me. A() is just a temporary value, not an assignable location on the stack, so it wouldn't seem to have an address. If it doesn't have an address, then I can't hold a reference to it. Okay, fine.

But wait! If I add the following conversion operator to the class A

class A
{
public:
  operator A&() { return *this; }
};

then all is well! My question is whether this even remotely safe. What exactly does this point to when A() is constructed as a temporary value?

I am given some confidence by the fact that

void foo(const A&) {}

can accept temporary values according to g++ and all other compilers I've used. The const keyword can always be cast away, so it would surprise me if there were any actual semantic differences between a const A& parameter and an A& parameter. So I guess that's another way of asking my question: why is a const reference to a temporary value considered safe by the compiler whereas a non-const reference is not?

Answer

Todd Gardner picture Todd Gardner · Jun 24, 2009

It isn't that an address can't be taken (the compiler could always order it shoved on the stack, which it does with ref-to-const), it's a question of programmers intent. With an interface that takes a A&, it is saying "I will modify what is in this parameter so you can read after the function call". If you pass it a temporary, then the thing it "modified" doesn't exist after the function. This is (probably) a programming error, so it is disallowed. For instance, consider:

void plus_one(int & x) { ++x; }

int main() {
   int x = 2;
   float f = 10.0;

   plus_one(x); plus_one(f);

   cout << x << endl << f << endl;
}

This doesn't compile, but if temporaries could bind to a ref-to-non-const, it would compile but have surprising results. In plus_one(f), f would be implicitly converted to an temporary int, plus_one would take the temp and increment it, leaving the underlying float f untouched. When plus_one returned, it would have had no effect. This is almost certainly not what the programmer intended.


The rule does occasionally mess up. A common example (described here), is trying to open a file, print something, and close it. You'd want to be able to do:

ofstream("bar.t") << "flah";

But you can't because operator<< takes a ref-to-non-const. Your options are break it into two lines, or call a method returning a ref-to-non-const:

ofstream("bar.t").flush() << "flah";