Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Lifetime extension misconception

15 views
Skip to first unread message

Frederick Gotham

unread,
Jul 29, 2006, 4:34:52 PM7/29/06
to

There is a common misconception, (one which I myself also held at one point),
that a const reference can "extend the lifetime of a temporary". Examples
such as the following are given:

Snippet (1)
-----------

#include <string>
using std::string;

#include <iostream>
using std::cout;

string Func()
{
return "Hello";
}

int main()
{
string const &str = Func();

cout << str << '\n';
}

In the above snippet, no temporary "has had its lifetime extended". If such a
thing were true, then the following code would be perfectly OK:

Snippet (2):
------------

#include <string>
using std::string;

string Func()
{
return "Hello";
}

int main()
{
string const &cstr = Func();

string &str = const_cast<string&>(cstr);

str = "World";
}


But alas, Snippet (2) exhibits undefined behaviour, because the reference
does not refer to the original non-const object which was returned by value
from the function.

The code in Snippet (1) works on exactly the same principle as the following
code snippet:

Snippet (3):
------------

int main()
{
int const &r = 5;
}

In Snippet (1) and in Snippet (3), the const reference remains valid NOT
because a temporary has had its liftime extended, but because the reference
was initialised with an R-value. The C++ Standard defines this process:

If the initializer expression is an rvalue, with T2 a class type, and “cv1
T1” is reference-compatible with “cv2 T2,” the reference is bound in one of
the following ways (the choice is implementation-defined):
— The reference is bound to the object represented by the rvalue (see 3.10)
or to a sub-object within that object.
— A temporary of type “cv1 T2” [sic] is created, and a constructor is called
to copy the entire rvalue object into the temporary. The reference is bound
to the temporary or to a sub-object within the temporary.

This explains why the following code snippet fails to compile, and disproves
that the lifetime of a temporary is extended by binding a const reference to
it.

Snippet (4):
------------

class MyClass {
private:
MyClass(MyClass const &); /* Can't copy-construct! */

public:
MyClass() {}
};

int main()
{
MyClass const &r = MyClass();
}

--

Frederick Gotham

Victor Bazarov

unread,
Jul 29, 2006, 4:42:11 PM7/29/06
to

Here is another interesting example:

char const* hello() { return "Hello"; }

#include <string>
#include <iostream>

int main() {
std::string const& str = hello();
std::cout << str << std::endl;
}

The temporary is not returned from anywhere, it's created as the result
of the initialisation of the reference. There is no lifetime "extention",
only the lifetime of the temporary to which the reference is bound. If
it is the original temporary, you can think of it as "extention" of its
lifetime, but the point you made with your private copy-c-tor constructor
is that it doesn't have to be the original temporary. It could just as
well be *another* temporary, copy-constructed from the "original" or from
something else, like in my example.

Just like in any other situation with returning by value, the copy-c-tor
has to be available for the code to be legal, even if copying is not done.

V
--
Please remove capital 'A's when replying by e-mail
I do not respond to top-posted replies, please don't ask


Old Wolf

unread,
Jul 31, 2006, 3:18:24 AM7/31/06
to
Frederick Gotham wrote:
> There is a common misconception, (one which I myself also held at one point),
> that a const reference can "extend the lifetime of a temporary".

That is not a misconception.

> #include <string>
> using std::string;
>
> #include <iostream>
> using std::cout;
>
> string Func()
> {
> return "Hello";
> }
>
> int main()
> {
> string const &str = Func();
>
> cout << str << '\n';
> }
>
> In the above snippet, no temporary "has had its lifetime extended".

The return value of Func() is a temporary. It has its lifetime extended
by being bound to 'str'.

If it did not have its lifetime extended, then the cout << str would be
referencing an object that no longer existed.

> If such a thing were true, then the following code would be perfectly OK:
>
> Snippet (2):
> ------------
>
> #include <string>
> using std::string;
>
> string Func()
> {
> return "Hello";
> }
>
> int main()
> {
> string const &cstr = Func();
>
> string &str = const_cast<string&>(cstr);
>
> str = "World";
> }

yes, that code is OK.

> But alas, Snippet (2) exhibits undefined behaviour,

No it doesn't.

> because the reference does not refer to the original non-const object
> which was returned by value from the function.

The reference refers to the return value of the function, not the
original object that was returned by value, as you say.

> The code in Snippet (1) works on exactly the same principle as the following
> code snippet:
>
> Snippet (3):
> ------------
>
> int main()
> {
> int const &r = 5;
> }
>
> In Snippet (1) and in Snippet (3), the const reference remains valid NOT
> because a temporary has had its liftime extended,

That code creates a temporary int , initializes it to 5, and then
binds 'r' to it. The temporary has its lifetime extended to the
scope of 'r'.

> but because the reference was initialised with an R-value.
> The C++ Standard defines this process:
>

> - A temporary of type "cv1 T2" [sic] is created, and a constructor is called


> to copy the entire rvalue object into the temporary. The reference is bound
> to the temporary or to a sub-object within the temporary.

Yes, that is what happened. A temporary of type "int" was created,
and its "copy constructor" was used to initialize it with 5. The
reference
is then bound to the temporary, and that temporary's lifetime is
extended.

I put "copy constructor" in quotes because ints don't really have copy
constructors. The string example is more straight forward.

> This explains why the following code snippet fails to compile

> and disproves that the lifetime of a temporary is extended by binding
> a const reference to it.

It does no such thing.


>
> Snippet (4):
> ------------
>
> class MyClass {
> private:
> MyClass(MyClass const &); /* Can't copy-construct! */
>
> public:
> MyClass() {}
> };
>
> int main()
> {
> MyClass const &r = MyClass();
> }

This fails to compile because the standard explicitly says that a
copy constructor is needed when initializing a reference with
a temporary. In fact you quoted that section of the standard earlier.

In this whole thread, I think you are confusing the object being
returned, with the return value. The return value (a temporary)
exists in the scope of the calling function, and is copy-constructed
from the object being returned (which exists in the scope of the
called function).

Normally the return value is destroyed at the end of the full-
expression, since it is a temporary. But when it is bound to
a const reference, it is not destroyed.

Frederick Gotham

unread,
Jul 31, 2006, 11:30:02 AM7/31/06
to
Old Wolf posted:

>> But alas, Snippet (2) exhibits undefined behaviour,
>
> No it doesn't.


Read the rules about binding a const reference to an R-value:

- A temporary of type "cv1 T2" [sic] is created, and a constructor is called
to copy the entire rvalue object into the temporary. The reference is bound
to the temporary or to a sub-object within the temporary.

Take note of the fifth word in the above paragraph -- "cv1".

Code Snippet (2) invokes Undefined Behaviour.

--

Frederick Gotham

Old Wolf

unread,
Jul 31, 2006, 7:56:56 PM7/31/06
to
Frederick Gotham wrote:
> Old Wolf posted:
>
> >> But alas, Snippet (2) exhibits undefined behaviour,
> >
> > No it doesn't.
>
> Read the rules about binding a const reference to an R-value:
>
> - A temporary of type "cv1 T2" [sic] is created, and a constructor is called
> to copy the entire rvalue object into the temporary. The reference is bound
> to the temporary or to a sub-object within the temporary.

Incidentally, this quote disproves your entire position: it says
clearly that a temporary is created, and the reference is bound to it.

The temporary continues to exist after the end of the declaration,
ie. it has its lifetime extended.

> Take note of the fifth word in the above paragraph -- "cv1".

What about it?

> Code Snippet (2) invokes Undefined Behaviour.

It doesn't. Here's the code again:

string const &cstr = Func();
string &str = const_cast<string&>(cstr);

The initialization of 'str' is covered by this part of 8.5.3#5:

A reference to type "cv1 T1" is initialized by an expression of
type "cv2 T2" as follows:
If the initializer expression is an lvalue (but is not a bitfield),
and "cv1 T1" is reference-compatible with "cv2 T2," or
[other case snipped], then the reference is bound directly
to the initializer expression lvalue

The initializer expression is "const_cast<string &>(cstr)", which
is an lvalue (*). cv1 and cv2 are both nothing, T1 is
"string" and T2 is "string&", so "cv1 T1" and "cv2 T2" are
reference-compatible (which is explained in 8.5.3).

So this section seems to covers the initialization.

Can you find any compiler that can't handle Snippet 2? gcc has
no problem with it.


(*) 5.2.11:
The result of the expression const_cast<T>(v) is of type T.
If T is a reference type, the result is an lvalue;

Noah Roberts

unread,
Jul 31, 2006, 8:10:49 PM7/31/06
to

Old Wolf wrote:

> string const &cstr = Func();
> string &str = const_cast<string&>(cstr);

Now, at this point is modifying the object through str defined or no?

Kai-Uwe Bux

unread,
Jul 31, 2006, 8:26:05 PM7/31/06
to
Old Wolf wrote:

> Frederick Gotham wrote:
>> Old Wolf posted:
>>
>> >> But alas, Snippet (2) exhibits undefined behaviour,
>> >
>> > No it doesn't.

I am afraid it does. I don't like it, but I can see the point.


>> Read the rules about binding a const reference to an R-value:
>>
>> - A temporary of type "cv1 T2" [sic] is created, and a constructor is
>> called to copy the entire rvalue object into the temporary. The reference
>> is bound to the temporary or to a sub-object within the temporary.
>
> Incidentally, this quote disproves your entire position: it says
> clearly that a temporary is created, and the reference is bound to it.
>
> The temporary continues to exist after the end of the declaration,
> ie. it has its lifetime extended.
>
>> Take note of the fifth word in the above paragraph -- "cv1".
>
> What about it?

We shall see below.



>> Code Snippet (2) invokes Undefined Behaviour.
>
> It doesn't. Here's the code again:
>
> string const &cstr = Func();
> string &str = const_cast<string&>(cstr);
>
> The initialization of 'str' is covered by this part of 8.5.3#5:

But the important part is the initialization of cstr: The compiler is free
to initialize it as follows:

a) a temporary X of type const string (this is where the cv1 kicks in!) is
created and initialized from the return value of Func().

b) this temporary (of type const string) is bound to cstr (and if you
insist, has it's lifetime extended).


Now, when you do the const_cast, you are casting away the constness of X
which has type const string. Any attempt to modify the value will then be
undefined behavior.

>
> A reference to type "cv1 T1" is initialized by an expression of
> type "cv2 T2" as follows:
> If the initializer expression is an lvalue (but is not a bitfield),
> and "cv1 T1" is reference-compatible with "cv2 T2," or
> [other case snipped], then the reference is bound directly
> to the initializer expression lvalue
>
> The initializer expression is "const_cast<string &>(cstr)", which
> is an lvalue (*). cv1 and cv2 are both nothing, T1 is
> "string" and T2 is "string&", so "cv1 T1" and "cv2 T2" are
> reference-compatible (which is explained in 8.5.3).
>
> So this section seems to covers the initialization.
>
> Can you find any compiler that can't handle Snippet 2? gcc has
> no problem with it.

Nope, and I think it is a defect in the standard.

>
> (*) 5.2.11:
> The result of the expression const_cast<T>(v) is of type T.
> If T is a reference type, the result is an lvalue;

Best

Kai-Uwe Bux

Old Wolf

unread,
Jul 31, 2006, 10:34:15 PM7/31/06
to
Kai-Uwe Bux wrote:
> Old Wolf wrote:
>> Frederick Gotham wrote:
>>>
>>> - A temporary of type "cv1 T2" [sic] is created, and a constructor is
>>> called to copy the entire rvalue object into the temporary. The reference
>>> is bound to the temporary or to a sub-object within the temporary.
>> Here's the code again:
>>
>> string const &cstr = Func();
>> string &str = const_cast<string&>(cstr);
>
> But the important part is the initialization of cstr: The compiler is free
> to initialize it as follows:
>
> a) a temporary X of type const string (this is where the cv1 kicks in!) is
> created and initialized from the return value of Func().
>
> b) this temporary (of type const string) is bound to cstr
>
> Now, when you do the const_cast, you are casting away the constness of X
> which has type const string. Any attempt to modify the value will then be
> undefined behavior.

Good explanation.

>> Can you find any compiler that can't handle Snippet 2? gcc has
>> no problem with it.
>
> Nope, and I think it is a defect in the standard.

Well, the standard says that the compiler has two options:
1) Bind the reference to the (non-const) return value
2) Bind the reference to a "cv1 T2" temporary constructed
from the return value.

So all these compilers that work, could simply be choosing option 1.

But it would seem to make more sense if the constructed temporary
had type "cv2 T2". Why does the standard say "[sic]" in it?

Also interesting is the earlier part of the same section that says that
if "cv2 T2" can be implicitly converted to an lvalue of type "cv3 t3",
then this conversion must be performed and bound to the reference:

A reference to type "cv1 T1" is initialized by an expression of
type "cv2 T2" as follows: If the initializer expression

- has a class type (i.e., T2 is a class type) and can be
implicitly converted to an lvalue of type "cv3 T3," where
"cv1 T1" is reference-compatible with "cv3 T3" 92)
(this conversion is selected by enumerating the
applicable conversion functions (13.3.1.6) and choosing
the best one through overload resolution (13.3)),

then the reference is bound directly to ... the lvalue result of the
conversion in the second case.

If we choose "cv3 t3" = "std::string", does that fit the criteria? A
std::string rvalue can be converted to a std::string lvalue by using
the copy-constructor!

Andrey Tarasevich

unread,
Aug 1, 2006, 12:23:41 PM8/1/06
to
Frederick Gotham wrote:
> There is a common misconception, (one which I myself also held at one point),
> that a const reference can "extend the lifetime of a temporary". Examples
> such as the following are given:
>
> Snippet (1)
> -----------
> ...

> string Func()
> {
> return "Hello";
> }
>
> int main()
> {
> string const &str = Func();
>
> cout << str << '\n';
> }
>
> In the above snippet, no temporary "has had its lifetime extended".

That's not correct. There's no definitive answer to what exactly happens there,
because it depends on implementation-defined behavior. The implementation is
free to choose one of two ways to initialize the reference: either 1) bind it
directly to the temporary object returned by 'Func', thus extending its
lifetime, or 2) create another temporary object of type 'const string' (by
copying the original temporary) and bind the reference to it, thus, again,
extending its lifetime. (The implementation is also free to apply the second
method repeatedly, until it eventually chooses the first method.)

Regardless of the method chosen by implementation, we have the lifetime of some
temporary extended here.

> If such a
> thing were true, then the following code would be perfectly OK:
>
> Snippet (2):
> ------------

> ...


> string Func()
> {
> return "Hello";
> }
>
> int main()
> {
> string const &cstr = Func();
>
> string &str = const_cast<string&>(cstr);
>
> str = "World";
> }
>
>
> But alas, Snippet (2) exhibits undefined behaviour, because the reference
> does not refer to the original non-const object which was returned by value
> from the function.

Incorrect. _In_ _general_ _case_, the reference indeed does not refer to the
original object. _In_ _general_ _case_ it is possible that the reference is
actually bound to a 'const string' object, which means that the code indeed
produces undefined behavior _in_ _general_ _case_.

However, if a concrete implementation defines its behavior so that the reference
is always bound to the original object returned by 'Func', then the code is
perfectly fine within the bounds of that implementation.

> The code in Snippet (1) works on exactly the same principle as the following
> code snippet:
>
> Snippet (3):
> ------------
>
> int main()
> {
> int const &r = 5;
> }

It _might_ work on exactly the same principle. The standard separated the cases
of class types and non-class types in 8.5.3/5 for a reason: there are
differences between reference initialization rules in those cases.

> In Snippet (1) and in Snippet (3), the const reference remains valid NOT
> because a temporary has had its liftime extended, but because the reference
> was initialised with an R-value.

Huh? Initializing a reference with an rvalue always results in some temporary
having its lifetime extended. How can you contrapose these two concepts?

The C++ Standard defines this process:
>
> If the initializer expression is an rvalue, with T2 a class type, and “cv1
> T1” is reference-compatible with “cv2 T2,” the reference is bound in one of
> the following ways (the choice is implementation-defined):
> — The reference is bound to the object represented by the rvalue (see 3.10)
> or to a sub-object within that object.
> — A temporary of type “cv1 T2” [sic] is created, and a constructor is called
> to copy the entire rvalue object into the temporary. The reference is bound
> to the temporary or to a sub-object within the temporary.
>
> This explains why the following code snippet fails to compile, and disproves
> that the lifetime of a temporary is extended by binding a const reference to
> it.
>
> Snippet (4):
> ------------
>
> class MyClass {
> private:
> MyClass(MyClass const &); /* Can't copy-construct! */
>
> public:
> MyClass() {}
> };
>
> int main()
> {
> MyClass const &r = MyClass();
> }
>

This doesn't disprove anything. In all "snippets" you provided so far some
temporary always has its lifetime extended. It might be completely different
temporary, not exactly the one you originally expected, but there;'s always one
that gets an extension.

--
Best regards,
Andrey Tarasevich

Andrey Tarasevich

unread,
Aug 1, 2006, 12:28:32 PM8/1/06
to
Old Wolf wrote:
> ...

> Also interesting is the earlier part of the same section that says that
> if "cv2 T2" can be implicitly converted to an lvalue of type "cv3 t3",
> then this conversion must be performed and bound to the reference:
>
> A reference to type "cv1 T1" is initialized by an expression of
> type "cv2 T2" as follows: If the initializer expression
> - has a class type (i.e., T2 is a class type) and can be
> implicitly converted to an lvalue of type "cv3 T3," where
> "cv1 T1" is reference-compatible with "cv3 T3" 92)
> (this conversion is selected by enumerating the
> applicable conversion functions (13.3.1.6) and choosing
> the best one through overload resolution (13.3)),
>
> then the reference is bound directly to ... the lvalue result of the
> conversion in the second case.
>
> If we choose "cv3 t3" = "std::string", does that fit the criteria? A
> std::string rvalue can be converted to a std::string lvalue by using
> the copy-constructor!

I don't see how you can perform such a conversion using a copy constructor.
Remember that temporary objects are not lvalues (as seen from "outside"). No,
the only way to convert an rvalue of class type to an lvalue is a user-defined
conversion operator returning a reference (which is mentioned in footnote 92).

Frederick Gotham

unread,
Aug 1, 2006, 12:40:49 PM8/1/06
to
Andrey Tarasevich posted:

> That's not correct. There's no definitive answer to what exactly happens
> there, because it depends on implementation-defined behavior.

(Apologies for the punctuation in the first sentence)

Would it be fair to say, that when, a const reference is bound to an R-value,
e.g.:

{
Type const &r = FuncReturnByValue();
}

That it's as if you had written:

{
Type const obj( FuncReturnByValue() );

Type const &r = obj;
}

--

Frederick Gotham

Victor Bazarov

unread,
Aug 1, 2006, 12:52:56 PM8/1/06
to

It could be that 'obj' is const, or it could be that 'obj' is not const.
And then again, during the construction of 'obj' there is probably a temp
object somewhere created, and a reference is bound to it, and so you get
the chicken and egg problem: you can't express reference binding to a temp
without resorting to reference binding to some other potential temp.

Andrey Tarasevich

unread,
Aug 1, 2006, 2:13:45 PM8/1/06
to
Frederick Gotham wrote:
> ...

> Would it be fair to say, that when, a const reference is bound to an R-value,
> e.g.:
>
> {
> Type const &r = FuncReturnByValue();
> }
>
> That it's as if you had written:
>
> {
> Type const obj( FuncReturnByValue() );
>
> Type const &r = obj;
> }

No and yes.

"No" because the implementation is free to choose to follow the above logic
(i.e. create an extra 'const' temporary), or decide to bind the reference
directly to the original temporary returned by the function. The original
temporary is not const (assuming the return value of 'FuncReturnByValue' is not
const).

"Yes" because in the portable code we have to assume that the most restrictive
behavior takes place: an extra const temporary is created.

Frederick Gotham

unread,
Aug 1, 2006, 2:31:25 PM8/1/06
to
Andrey Tarasevich posted:

> No, the only way to convert an rvalue of class type to
> an lvalue is a user-defined conversion operator returning a reference
> (which is mentioned in footnote 92).


Perhaps something like:

template<class T>
struct Temp {
T obj;

T &operator&()
{
return obj;
}
};

struct MyType {};

void TakeRef(MyType&) {}

int main()
{
TakeRef( &Temp<MyType>() );
}

--

Frederick Gotham

Frederick Gotham

unread,
Aug 1, 2006, 2:47:00 PM8/1/06
to
Frederick Gotham posted:

> template<class T>
> struct Temp {
> T obj;
>
> T &operator&()
> {
> return obj;
> }
> };
>
> struct MyType {};
>
> void TakeRef(MyType&) {}
>
> int main()
> {
> TakeRef( &Temp<MyType>() );
> }


Bad example!

I had intended to overload the "address of" operator in order to return the
address of the "temporary" object.

(I realise that using it to return a reference is an abuse of operator
overloading!)

--

Frederick Gotham

0 new messages