-->

Can't pass temporary object as reference

2020-01-25 00:50发布

问题:

This is a very minimal example:

class Foo
{
public:
    Foo(int x) {};
};

void ProcessFoo(Foo& foo)
{
}

int main()
{
    ProcessFoo(Foo(42));
    return 0;
}

The above compiles fine on Visual Studio, but generates an error on Linux and Mac.

Compiling the above generates this:

$ g++ -std=c++11 -c newfile.cpp

newfile.cpp: In function ‘int main()’:
newfile.cpp:23:23: error: invalid initialization of non-const reference of type ‘Foo&’ from an rvalue of type ‘Foo’
     ProcessFoo(Foo(42));
                       ^
newfile.cpp:14:6: note: in passing argument 1 of ‘void ProcessFoo(Foo&)’
 void ProcessFoo(Foo& foo)

I've found three workarounds:

  1. Avoid the use of an inline a temp variable for the invocation of ProcessFoo.

Like this:

Foo foo42(42);
ProcessFoo(foo42);
  1. ProcessFoo takes a const reference: void ProcessFoo(const Foo& foo)

  2. ProcessFoo just lets Foo get passed by value. void ProcessFoo(Foo foo)

Why is the compiler forbidding my original code? (What is it guarding against)? What is it about each of the three workarounds above that satisfies the compiler? What would MSVC allow it, but not g++?

回答1:

By design, C++ only allows a temporary to be passed to a const reference, value, or rvalue reference. The idea is that a function taking a non-const reference parameter is stating that it wants to modify the parameter and allowing it to go back to the caller. Doing so with a temporary is meaningless and most likely an error.

And I don't know what version of g++ you're running. It doesn't work here: http://coliru.stacked-crooked.com/a/43096cb398cbc973



回答2:

Why is the compiler forbidding my original code?

Because it is forbidden by the Standard:

8.5.3 References 5
...
Otherwise, the reference shall be an lvalue reference to a non-volatile const type (i.e., cv1 shall be const), or the reference shall be an rvalue reference.
[ Example:
double& rd2 = 2.0; // error: not an lvalue and reference not const
...

'

What is it guarding against?

Inadvertently modifying an object that is going to be destructed after the function call.

What is it about each of the three workarounds above that satisfies the compiler?

1 Creates a named object and 3 a copy.
2 Works because the lifetime of the object is simply extended, and changes to it are prevented at the same time.

What would MSVC allow it, but not g++?

Because it is a language extension. Disable it by going to Property Pages->C/C++->Language->Disable Language Extensions and you'll get an error.



回答3:

Why is the compiler forbidding my original code?

MSVC has an extension that allows temporaries to bind to non-const lvalue-references. Of course this isn't a standard-conforming feature so I would stay away from it to be portable. For example, it doesn't work with the latest versions of GCC and Clang as you've seen.

What is it about each of the three workarounds above that satisfies the compiler?

Back in C++03, expressions could only be lvalues or rvalues. References could only designate the "lvalueness" of an object, and so it was used with the intention of aliasing a preexisting object. By contrast, rvalues don't exist beyond the expression in which they appear. Also, the end result of references was normally to copy or modify the object, and it doesn't make much sense to the language to modify an rvalue like 55 for example.

The rules allow you to bind an rvalue to a lvalue-reference to const, in which case the temporary's lifetime is extended to the lifetime of the reference. When you take an object by value the object is copied.

With C++11 we have rvalue-references and xvalues which were made for the purpose of exchanging ownership. With this is lessens the usefulness of lvalue-references to const. Moreover, taking by-value causes a move if it is an rvalue.



回答4:

Once you declared the prototype for ProcessFoo as

void ProcessFoo(Foo& foo)

You are conveying your intent as the formal parameter "foo" is subject to modification as it is not being passed by const &.

At the call-site,

ProcessFoo(Foo(42));

Foo(42) is creating a temporary stack object that is not modifiable. It is okay to pass-by-value or pass-by-ref-to-const to a method.

As you listed yourself, satisfying those constraints, makes the compiler happy.

  1. is giving you an object that is not compiler generated and is under your control.
  2. Informs the compiler that the method guarantees const-ness of the object.
  3. Informs the compiler that this (temporary) object is passed by value and hence no issues.