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?
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";