views:

688

answers:

3

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?

+2  A: 

When you assign an r-value to a const reference, you are guaranteed that the temporary won't be destroyed until the reference is destroyed. When you assign to a non-const reference, no such guarantee is made.

int main()
{
   const A& a2= A(); // this is fine, and the temporary will last until the end of the current scope.
   A& a1 = A(); // You can't do this.
}

You can't safely cast away const-ness willy nilly and expect things to work. There are different semantics on const and non-const references.

Eclipse
+9  A: 

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, because float would be converted to an temp int, the function would take the temp, increment it, and effectively doing nothing.


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";
Todd Gardner
I agree that this sort of thing is usually a programmer error. In case you're curious, the class I'm trying to pass as a reference is a sort of smart pointer, which has an underlying heap-allocated pointer member. I actually do want the receiving function to do things with the underlying pointer. I just want to be sure the temporary smart pointer is not destroyed (decrementing the reference count) until after the receiving function returns.
Ben
If that's your intent, either declare the smart pointer on the stack explicitly and pass it in or pass it in by value. If you pass it in by value you are guaranteed that it will not be destroyed until the function exits.
MSN
@Ben - added some to my post about it. Can't say if it is a good idea for you without seeing your code, but there are some use cases (and it won't be destroyed until it returns)
Todd Gardner
@Todd Much appreciated.
Ben
A: 

A gotcha that some people may run into: the MSVC compiler (Visual Studio compiler, verified with Visual Studio 2008) will compile this code with no problems. We had been using this paradigm in a project for functions that usually took one argument (a chunk of data to digest), but sometimes wanted to search the chunk and yield results back to the caller. The other mode was enabled by taking three arguments---the second argument was the information to search on (default reference to empty string), and the third argument was for the return data (default reference to empty list of the desired type).

This paradigm worked in Visual Studio 2005 and 2008, and we had to refactor it so that the list was built and returned instead of owned-by-caller-and-mutated to compile with g++.

If there is a way to set the compiler switches to either disallow this sort of behavior in MSVC or allow it in g++, I would be excited to know; the permissiveness of the MSVC compiler / restrictiveness of the g++ compiler adds complications to porting code.

fixermark