Question

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?

Was it helpful?

Solution

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

OTHER TIPS

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.

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top