About Binding a Const Reference to a Sub-Object of a Temporary

About binding a const reference to a sub-object of a temporary

This is covered by CWG 1651:

The resolution of issues 616 and 1213, making the result of a member
access or subscript expression applied to a prvalue an xvalue, means
that binding a reference to such a subobject of a temporary does not
extend the temporary's lifetime. 12.2 [class.temporary] should be
revised to ensure that it does.

The status quo is that only prvalues are treated as referring to temporaries - thus [class.temporary]/5 ("The second context is when a reference is bound to a temporary.") is not considered applicable. Clang and GCC have not actually implemented issue 616's resolution, though. center().x is treated as a prvalue by both. My best guess:

  • GCC simply didn't react to any DRs yet, at all. It doesn't extend lifetime when using scalar subobjects, because those are not covered by [dcl.init.ref]/(5.2.1.1). So the complete temporary object doesn't need to live on (see aschelper's answer), and it doesn't, because the reference doesn't bind directly. If the subobject is of class or array type, the reference binds directly, and GCC extends the temporary's lifetime. This has been noted in DR 60297.

  • Clang recognizes member access and implemented the "new" lifetime extension rules already - it even handles casts. Technically speaking, this is not consistent with the way it handles value categories. However, it is more sensible and will be the correct behavior once the aforementioned DR is resolved.

I'd therefore say that GCC is correct by current wording, but current wording is defective and vague, and Clang already implemented the pending resolution to DR 1651, which is N3918. This paper covers the example very clearly:

If E1 is a temporary expression and E2 does not designate a
bit-field, then E1.E2 is a temporary expression.

center() is a temporary expression as per the paper's wording for [expr.call]/11. Thus its modified wording in the aforementioned [class.temporary] /5 applies:

The second context is when a reference does not bind directly (8.5.3
dcl.init.ref) or is initialized with a temporary expression (clause 5). The corresponding temporary
object (if any) persists for the lifetime of the reference
except: [...inapplicable exceptions...]

Voilà, we have lifetime extension. Note that "the corresponding temporary object" is not clear enough, one of the reasons for the proposal's deferment; it will assuredly be adopted once it gets revised.


is an xvalue (but not a bit-field), class prvalue, array prvalue or function lvalue and “cv1 T1” is reference-compatible with “cv2 T2”, or […]

Indeed, GCC respects this fully and will extend lifetime if the subobject has array type.

const reference to member of temporary object

Yes, this code is perfectly acceptable. The rules, according to the standard are ([class.temporary]):


  1. There are two contexts in which temporaries are destroyed at a
    different point than the end of the fullexpression. The first context
    is when a default constructor is called to initialize an element of an
    array. If the constructor has one or more default arguments, the
    destruction of every temporary created in a default argument is
    sequenced before the construction of the next array element, if any.

  2. The second context is when a reference is bound to a temporary. The
    temporary to which the reference is bound or the temporary that is the
    complete object of a subobject to which the reference is bound

    persists for the lifetime of the reference...

As you can see the highlighted line makes it clear that binding reference to sub-objects is acceptable, as the complete object has to have its lifetime extended as well.

Note that first does qualify as a subobject [intro.object]:


  1. Objects can contain other objects, called subobjects. A subobject can
    be a member subobject (9.2), a base class subobject (Clause 10), or an
    array element. An object that is not a subobject of any other object
    is called a complete object.

Extending the life of a temporary object by getting a reference to a subobject

Is the verbiage used by the standard a defect?

No.

Is the compiler non-conformant?

Yes. As was pointed out in a comment, it has been fixed in a newer version of g++, g++ 7. Related link: http://gcc.gnu.org/bugzilla/show_bug.cgi?id=54293.

Is the verbiage used by cppreference.com a defect?

Yes. The contents of the page at cppreference.com has been updated.

How come a non-const reference cannot bind to a temporary object?

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 ...

Basically, you shouldn't try to modify temporaries for the very reason that they are temporary objects and will die any moment now. The reason you are allowed to call non-const methods is that, well, you are welcome to do some "stupid" things as long as you know what you are doing and you are explicit about it (like, using reinterpret_cast). But if you bind a temporary to a non-const reference, you can keep passing it around "forever" just to have your manipulation of the object disappear, because somewhere along the way you completely forgot this was a temporary.

If I were you, I would rethink the design of my functions. Why is g() accepting reference, does it modify the parameter? If no, make it const reference, if yes, why do you try to pass temporary to it, don't you care it's a temporary you are modifying? Why is getx() returning temporary anyway? If you share with us your real scenario and what you are trying to accomplish, you may get some good suggestions on how to do it.

Going against the language and fooling the compiler rarely solves problems - usually it creates problems.



Edit: Addressing questions in comment:
1) `X& x = getx().ref(); // OK when will x die?` - I don't know and I don't care, because this is exactly what I mean by "going against the language". The language says "temporaries die at the end of the statement, unless they are bound to const reference, in which case they die when the reference goes out of scope". Applying that rule, it seems x is already dead at the beginning of the next statement, since it's not bound to const reference (the compiler doesn't know what ref() returns). This is just a guess however.

  1. I stated the purpose clearly: you are not allowed to modify temporaries, because it just does not make sense (ignoring C++0x rvalue references). The question "then why am I allowed to call non-const members?" is a good one, but I don't have better answer than the one I already stated above.

  2. Well, if I'm right about x in X& x = getx().ref(); dying at the end of the statement, the problems are obvious.

Anyway, based on your question and comments I don't think even these extra answers will satisfy you. Here is a final attempt/summary: The C++ committee decided it doesn't make sense to modify temporaries, therefore, they disallowed binding to non-const references. May be some compiler implementation or historic issues were also involved, I don't know. Then, some specific case emerged, and it was decided that against all odds, they will still allow direct modification through calling non-const method. But that's an exception - you are generally not allowed to modify temporaries. Yes, C++ is often that weird.

Is a const reference bound to another reference which is cast from temporary a dangling reference?

According to the newest draft [class.temporary]/6 (irrelevant part is elided by me):

The third context is when a reference is bound to a temporary object. The temporary object to which the reference is bound or the temporary object that is the complete object of a subobject to which the reference is bound persists for the lifetime of the reference if the glvalue to which the reference is bound was obtained through one of the following:

  • ...

  • a const_­cast ([expr.const.cast]), static_­cast ([expr.static.cast]), dynamic_­cast ([expr.dynamic.cast]), or reinterpret_­cast ([expr.reinterpret.cast]) converting, without a user-defined conversion, a glvalue operand that is one of these expressions to a glvalue that refers to the object designated by the operand, or to its complete object or a subobject thereof,

  • ...


... [ Note: An explicit type conversion ([expr.type.conv], [expr.cast]) is interpreted as a sequence of elementary casts, covered above. [ Example:

const int& x = (const int&)1;  // temporary for value 1 has same lifetime as x

— end example ] — end note ]

Your code is well-formed.


Before C++14, the wording in the standard is unclear about such case, and there is a defect issue 1376. This issue clarifies the lifetime of the temporary object should not be extended in such case. However, this clarification is superseded by issue 1299 (whose resolution is not included even in C++17, but in the current draft).

So you can conclude that before the resolution of issue 1299, it is a bug for GCC with version after 4.6. There is also a bug report 52202 for GCC.

Is reference binding to a temporary of a temporary undefined behavior?

In order for lifetime extension to occur, the reference must bind to a prvalue (which is then materialized into a temporary object so that the reference has something to bind to). However, capsule.sphere().radius() is not a prvalue; it is an lvalue that refers to Sphere::_radius. Therefore lifetime extension does not occur, and the Sphere object together with its _radius member will be destroyed at the end of the full-expression.

const reference to a temporary object becomes broken after function scope (life time)

The lifetime-extension of a temporary object can be performed only once, when the temporary object gets bound to the first reference. After that, the knowledge that the reference refers to a temporary object is gone, so further lifetime extensions are not possible.

The case that is puzzling you

A const& refnop = A(a).nothing();

is similar to this case:

A const& foo(A const& bar)
{
return bar;
}
//...
A const& broken = foo(A());

In both cases, the temporary gets bound to the function argument (the implicit this for nothing(), bar for foo()) and gets its lifetime 'extended' to the lifetime of the function argument. I put 'extended' in quotes, because the natural lifetime of the temporary is already longer, so no actual extension takes place.

Because the lifetime extension property is non-transitive, returning a reference (that happens to refer to a temporary object) will not further extend the lifetime of the temporary object, with as result that both refnop and broken end up referring to objects that no longer exist.

Prolonging life of a temporary object using const reference

The two links which you have referred are different in the sense that one shows the use of a local const reference and other shows the use of a class member const reference.

When we create local const references and refer to a temporary object then in this compiler extends the life of the temporary till the scope of local const reference.

Class member const reference pointing to temporary will lead to unexpected results as the life of the temporary object will not be extended beyond the constructor invoked to initialize class member reference. As explained in one of the answers the temporary will only survive till completion of the constructor.

Quoting the answer from:
Does a const reference prolong the life of a temporary?

The lifetime extension is not transitive through a function argument. §12.2/5 [class.temporary]:

The second context is when a reference is bound to a temporary. The temporary to which the reference is bound or the temporary that is the complete object to a subobject of which the temporary is bound persists for the lifetime of the reference except as specified below. A temporary bound to a reference member in a constructor’s ctor-initializer (§12.6.2 [class.base.init]) persists until the constructor exits. A temporary bound to a reference parameter in a function call (§5.2.2 [expr.call]) persists until the completion of the full expression containing the call.

If you analyze it correctly you will realize that in both cases the life of temporary is extended till the scope from where the references are initialized is valid. As soon as the scope from where reference goes out of scope the temporary becomes invalid.

For local const reference, scope is inside a function from where it is being initialized to a temp.
For class member const reference, scope is constructor where it is being initialized to a temp.

You should also read this GOTW article:
https://herbsutter.com/2008/01/01/gotw-88-a-candidate-for-the-most-important-const/

Why doesn't a const reference extend the life of a temporary object passed via a function?

It's by design. In a nutshell, only the named reference to which the temporary is bound directly will extend its lifetime.

[class.temporary]

5 There are three contexts in which temporaries are destroyed at a
different point than the end of the full-expression. [...]

6 The third context is when a reference is bound to a temporary.
The temporary to which the reference is bound or the temporary that is
the complete object of a subobject to which the reference is bound
persists for the lifetime of the reference except:

  • A temporary object bound to a reference parameter in a function call persists until the completion of the full-expression containing
    the call.
  • The lifetime of a temporary bound to the returned value in a function return statement is not extended; the temporary is destroyed
    at the end of the full-expression in the return statement.
  • [...]

You didn't bind directly to ref2, and you even pass it via a return statement. The standard explicitly says it won't extend the lifetime. In part to make certain optimizations possible. But ultimately, because keeping track of which temporary should be extended when a reference is passed in and out of functions is intractable in general.

Since compilers may optimize aggressively on the assumption that your program exhibits no undefined behavior, you see a possible manifestation of that. Accessing a value outside its lifetime is undefined, this is what return ref2; does, and since the behavior is undefined, simply returning zero is a valid behavior to exhibit. No contract is broken by the compiler.



Related Topics



Leave a reply



Submit