Reputation: 18063
I'd like to pass a reference into a function. This code does not work, as I'd expect:
struct A {
};
void foo(A& a) {
// do something with a
}
int main(int, char**) {
foo(A());
}
I get the compile error
invalid initialization of non-const reference of type
A&
from an rvalue of typeA
But when I just add the method A& ref()
to A
like below and call it before passing it along, it seems I can use a
. When debugging, the A
object is destroyed after foo()
is called:
struct A {
A& ref() {
return *this;
}
};
void foo(A& a) {
// do something with a
}
int main(int, char**) {
foo(A().ref());
}
Is this valid code according to the standard? Does calling ref()
magically extend the lifetime of the object until foo()
returns?
Upvotes: 16
Views: 3240
Reputation: 304182
There are several questions in this question. I'll attempt to address all of them:
First, you cannot pass a temporary (prvalue) of type A
to a function taking A&
because non-const lvalue references cannot bind to rvalues. That's a language restriction. If you want to be able to pass a temporary, you either need to take a parameter of type A&&
or of type A const&
- the latter since temporaries can bind to const lvalue references.
Is this valid code according to the standard? Does calling
ref()
magically extend the lifetime of the object untilfoo()
returns?
There is no lifetime extension going on in your program at all. From [class.temp]:
There are three contexts in which temporaries are destroyed at a different point than the end of the full-expression. The first context is when a default constructor is called to initialize an element of an array with no corresponding initializer (8.6). The second context is when a copy constructor is called to copy an element of an array while the entire array is copied (5.1.5, 12.8). [...] The third context is when a reference is bound to a temporary.
None of those contexts apply. We are never binding a reference to a temporary in this code. ref()
binds *this
to an A&
, but *this
is not a temporary, and then that resulting reference is simply passed into foo()
.
Consider this variant of your program:
#include <iostream>
struct A {
A& ref() { return *this; }
~A() { std::cout << "~A()\n"; }
};
int main() {
auto& foo = A().ref();
std::cout << "----\n";
}
which prints
~A()
----
illustrating that there is no lifetime extension.
If instead of binding the result of ref()
to a reference we instead bound a member:
#include <iostream>
struct A {
A& ref() { return *this; }
int x;
~A() { std::cout << "~A()\n"; }
};
int main() {
auto&& foo = A().x;
std::cout << "----\n";
}
then we actually are binding a temporary to a reference and that third context applies - the lifetime of the complete object of the subobject to which the reference is bound is persisted for the lifetime of the reference. So this code prints:
----
~A()
Upvotes: 5
Reputation: 85541
Your code is perfectly valid.
In this line
foo(A().ref());
The instance of a temporary A
lives until the end of the statement (;
).
That's why it's safe to pass A&
returned from ref()
to foo
(as long as foo
doesn't store it).
ref()
by itself does not extend any lifetime, but it helps by returning an lvalue reference.
What happens in the case of foo(A());
? Here the temporary is passed as an rvalue. And in C++ an rvalue does not bind to non-const lvalue references (even in C++11, an rvalue reference does not bind to non-const lvalue references).
From this Visual C++ blog article about rvalue references:
... C++ doesn't want you to accidentally modify temporaries, but directly calling a non-const member function on a modifiable rvalue is explicit, so it's allowed ...
Upvotes: 23
Reputation: 76523
A()
creates a temporary object of type A
. The object exists until the end of the full expression in which it is created. The problem in your original code is not the lifetime of this temporary; it's that the function takes its argument as a non-const reference, and you're not allowed to pass a temporary object as a non-const reference. The simplest change is for foo
to take it's argument by const reference, if that's appropriate to what the function does:
void foo(const A&);
int main() {
foo(A());
}
Upvotes: 7