martinus
martinus

Reputation: 18063

Is the lifetime of a reference extended?

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 type A

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

Answers (3)

Barry
Barry

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 until foo() 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

rustyx
rustyx

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

Pete Becker
Pete Becker

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

Related Questions