5

The C++ standard draft N4296 says

[class.temporary/5] 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 except...

So I want to know what happens if two or more references are bound to a temporary. Is it specific in the standard? The following code may be an example:

#include <iostream> //std::cout
#include <string>   //std::string
const std::string &f() {
    const std::string &s = "hello";
    static const std::string &ss = s;
    return ss;
}
int main() {
    const std::string &rcs = f();
    std::cout << rcs; //empty output
                      //the lifetime of the temporary is the same as that of s
    return 0;
}

If we change the bounding order, the case is different.

#include <iostream> //std::cout
#include <string>   //std::string
const std::string &f() {
    static const std::string &ss = "hello";
    const std::string &s = ss;
    return ss;
}
int main() {
    const std::string &rcs = f();
    std::cout << rcs; //output "hello"
                      //the lifetime of the temporary is the same as that of ss
    return 0;
}

The compilation is done on Ideone.com.

I guess [class.temporary/5] only holds when the first reference is bound to the temporary, but I cannot find an evidence in the standard.

xskxzr
  • 12,442
  • 12
  • 37
  • 77
  • 2
    You cannot bind a temporary to more than one reference. The only temporary (= prvalue) in your example is the `std::string` object created from `"hello"`, and it is bound to the first reference variable. The second reference is bound to the lvalue resulting from evaluating the id-expression containing the first variable. – Kerrek SB Nov 21 '15 at 17:58
  • 1
    @KerrekSB "You cannot bind a temporary to more than one reference" ... guess again http://coliru.stacked-crooked.com/a/edfbe82db89ea4ab – Lorah Attkins Nov 21 '15 at 18:03
  • @Kerrek SB [N4296/expr/5] says "If an expression initially has the type 'reference to T' (8.3.2, 8.5.3), the type is adjusted to T prior to any further analysis. The expression designates the object or function denoted by the reference, and the expression is an lvalue or an xvalue, depending on the expression." So I think the second reference is indeed bound to the temporary. – xskxzr Nov 21 '15 at 18:20
  • 1
    Here's a quote from cppreference (not normative): *"In general, the lifetime of a temporary cannot be further extended by "passing it on": a second reference, initialized from the reference to which the temporary was bound, does not affect its lifetime."* – David G Nov 21 '15 at 18:46
  • @LorahAttkins: And who's the temporary in your example? :-) – Kerrek SB Nov 21 '15 at 18:54
  • 1
    @Shenke: But a temporary must be a prvalue. If it's not a prvalue, it's not temporary anymore. – Kerrek SB Nov 21 '15 at 18:55
  • @KerrekSB I was wrong :/ – Lorah Attkins Nov 21 '15 at 19:03
  • @KerrekSB Got it. Thanks! – xskxzr Nov 21 '15 at 19:03
  • @KerrekSB: A temporary is an object. Objects don't have value categories; expressions do. – Lightness Races in Orbit Nov 21 '15 at 19:18

3 Answers3

5

This is a defect in that section that I reported as http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1299 .

The proposed resolution is to add a term "temporary expressions". Life-time extension only happens for objects referred to by temporary expressions.

Here's my original report which I privately emailed. I think it makes clear what it's about

In the model of the Standard, there appears to be a distinction about temporary objects, and temporary expressions.

Temporary objects are created by certain operations operations, like functional casts to class types. Temporary objects have a limited specified lifetime.

Temporary expressions are expressions that are so attributed because they are used to track whether or not an expression refers to a temporary object for the purpose of determining whether or not the lifetime of their referent is lengthened when bound by a reference. Temporary expressions are compile time entities.

Several paragraphs refer to "temporaries", but do not explicitly specify whether they refer to temporary objects referred to by arbitrary expressions, or whether they refer only to temporary expressions. The paragraphs about RVO (paragraph 12.8p31) use "temporary" in the sense of temporary objects (they say such things like "temporary class object that has not been bound to a reference"). The paragraphs about lifetime lengthening (sub-clause 12.2) refer to both kinds of temporaries. For example, in the following, "*this" is not regarded as a temporary, even though it refers to a temporary

struct A { A() { } A &f() { return *this; } void g() { } };

// call of g() is valid: lifetime did not end prematurely
// at the return statement
int main () { A().f().g(); }

As another example, core issue 462 handles about temporary expressions (making a comma operator expression a temporary, if the left operand was one). This appears to be very similar to the notion of "lvalue bitfields". Lvalues that track along that they refer to bitfields at translation time, so that reads from them can act accordingly and that certain reference binding scenarios can emit diagnostics.

Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
  • @Johannes: Is there a difference between "temporary expression" and "rvalue"? I'm thinking that with the C++11 variants of rvalue surely one must fit? – Cheers and hth. - Alf Nov 21 '15 at 19:55
  • @Cheersandhth.-Alf: It needs to be a prvalue. Xvalues definitely don't do, so with `struct X { int a; }`, `int && r = X{}.a;` does *not* extend lifetimes. (Since C++14, the RHS is an xvalue.) – Kerrek SB Nov 21 '15 at 20:35
  • @KerrekSB: Forgive me for just fishing (I am unclear on this issue), but there is wording about extending the lifetime of an object when a part of it is bound to a reference. What would be an example of that? – Cheers and hth. - Alf Nov 21 '15 at 20:36
  • Oh, `Base const& o = Derived()` would qualify, sorry. – Cheers and hth. - Alf Nov 21 '15 at 20:41
  • @Cheersandhth.-Alf: Precisely :-) Mind you, the standard is still pretty unclear about this, e.g. when binding array elements: `int && a = T()[4];` with `using T = int[10];` etc. – Kerrek SB Nov 21 '15 at 21:15
  • @Cheersandhth.-Alf `0` is not a temporary expression (it does not refer to a temporary object). But it is a prvalue. – Johannes Schaub - litb Nov 21 '15 at 21:18
  • @ᐅJohannesSchaub-litbᐊ: But `int const& nada = 0;` is perfectly OK, isn't it? And the `0` is not a temporary and you say it's not a temporary expression. So do we conclude that the right fix is for the standard to use the term *prvalue*? – Cheers and hth. - Alf Nov 21 '15 at 21:23
  • @Cheersandhth.-Alf For class prvalues, I think they are always temporary expressions (at least once they fixed `prvalue.member` to never be an xvalue if the member is a nonstatic member. Previously it was a non-temporary prvalue), same for arrays. – Johannes Schaub - litb Nov 21 '15 at 21:24
  • @Cheersandhth.-Alf I am not sure, but I would be inclined to have this be more clearer and not use "prvalue". Because I think I would like to have `(temporary expression).nonStaticMember` be a temporary expression, even though it is an xvalue. Because it is a subobject of a temporary object and it is supposed to be lifetime extended if bound by a reference. – Johannes Schaub - litb Nov 21 '15 at 21:27
  • @KerrekSB I think what you said above about `X{}.a` not being lifetime extended since C++14 is false. We always have had a special rule about the case that a member access expression on a temporary life-time extends the complete (temporary) object of the member object. – Johannes Schaub - litb Nov 21 '15 at 21:33
  • @Cheersandhth.-Alf just to be as clear as possible: `int const& nada = 0` works not because `0` is a prvalue, but because the reference isn't actually bound to `0`, but bound to a temporary initialized from `0`. – Johannes Schaub - litb Nov 21 '15 at 21:37
  • Yah, my point was (just that) the fix has to permit this case. – Cheers and hth. - Alf Nov 21 '15 at 21:52
  • It should be sufficient to change the rule on lifetime extension to require **direct** binding, since 8.5.3 defines *direct binding* to include exactly the right set of cases. – Ben Voigt Nov 21 '15 at 22:19
3

What you need to know is that a reference return type does not count as a temporary for the purposes of this section, and will never cause lifetime extension.

(Pedantic note: The standard requires that the reference be bound to an xvalue, not merely to a temporary. The second reference is bound via an lvalue, not an xvalue.)

Your first example returns a dangling reference -- the cout line is undefined behavior. It could print Hello! and that would prove nothing.

Here's a simpler example:

template<class T>
const T& ident(const T& in) { return in; }

int main(void)
{
    const X& ref1 = X(1); // lifetime extension occurs
    const X& ref2 = ident(X(2)); // no lifetime extension
    std::cout << "Here.\n";
}

The order of construction and destruction is:

X(1)
X(2)
~X() for X(2) object
"Here." is printed
~X() for X(1) object
Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • Thanks @0x499602D2: Good catch, it wasn't demonstrating what I wanted to. Now fixed. – Ben Voigt Nov 21 '15 at 18:53
  • Re the xvalue requirement, I fail to find it. Can you provide a reference? – Cheers and hth. - Alf Nov 21 '15 at 21:18
  • @Alf: *Direct binding* is related to *xvalue* and *prvalue* (which are *xvalues*) in the last several paragraphs of 8.5.3. Temporary lifetime extension occurs only for *direct binding*, although as Johannes points out, the rule for lifetime extension omits the word *direct*, it is only implied. – Ben Voigt Nov 21 '15 at 22:16
  • @BenVoigt I think that is false or I don't understand what you are saying. `For int &&a = 0;` there is lifetime extension, but the binding of the temporary created during the reference initialization doesn't have any kind of binding, neither direct nor indirect, while the binding of `0` to the reference has indirect binding (because only the temporary created will be bound). – Johannes Schaub - litb Nov 22 '15 at 11:29
  • @ᐅJohannesSchaub-litbᐊ: Hmmm, my reading of 8.5.3 says that expression does *bind directly*, but on further inspection, the wording of 8.5.3 in n4527 appears to be badly broken by incomplete edits. – Ben Voigt Nov 22 '15 at 13:20
  • @Ben the wording is "Otherwise, a temporary of type “ cv1 T1” is created and copy-initialized ([dcl.init]) from the initializer expression. The reference is then bound to the temporary." which is the last bullet-distinguishment and "In all cases except the last (i.e., creating and initializing a temporary from the initializer expression), the reference is said to bind directly to the initializer expression.". Hence there is indirect binding of `0`. Also note that the binding is to the initializer expression (that is, `0`). This looks reasonably clear to me. – Johannes Schaub - litb Nov 22 '15 at 14:12
  • @ᐅJohannesSchaub-litbᐊ: in n4527, the last case (the one that's said to not be direct binding) is "If T1 is reference-related to T2: cv1 shall be the same cv-qualification as, or greater cv-qualification than, cv2 ; and if the reference is an rvalue reference, the initializer expression shall not be an lvalue." – Ben Voigt Nov 22 '15 at 14:14
  • @BenVoigt that bullet is not the " last case" referred to, but is just constraining the "Otherwise:" cases before it with certain rules. Saying that "Foo shall not be bar" is a direct or indirect binding does not seem to make sense. – Johannes Schaub - litb Nov 22 '15 at 14:20
  • @Ben see https://stackoverflow.com/questions/33855956/reference-initialization-and-direct-vs-indirect-binding . – Johannes Schaub - litb Nov 22 '15 at 14:37
1

The first function presented in the question,

const std::string &f() {
    const std::string &s = "hello";
    static const std::string &ss = s;
    return ss;
}

yields Undefined Behaviour if the returned reference is used. The referred to object ceases to exist when the first call of the function returns. And on subsequent calls ss is a dangling reference.

The context of the standard's lifetime extension paragraph is

C++11 §12.2/4

There are two contexts in which temporaries are destroyed at a different point than the end of the full-expression

I.e., this is all about about temporaries that otherwise would be destroyed at the end of the full-expression producing them.

One of the two contexts is, with four noted exceptions,

C+11 §12.2/5

… when a reference is bound to [such a] a temporary

In the code above the temporary std::string produced by the full-expression "hello" is bound to the reference s and lifetime extended to the scope of s, which is the function body.

The subsequent declaration and initialization of a static reference ss does not involve a full-expression that creates temporary. Its initializer expression s is not a temporary: it's a reference to a local. Hence it's outside the context covered by the lifetime extension paragraph.

But how do we know that that's what's meant? Well, keeping track of whether a reference dynamically refers to something that originally was a temporary, is non-computable for the general case, and the C++ language standard does not involve such far fetched concepts. So it's simple, really.


An IMHO more interesting case, wrt. the formal rules, is

#include <string>
#include <iostream>
using namespace std;

template< class Type >
auto temp_ref( Type&& o ) -> T& { return o; }

auto main() -> int
{
    auto const& s = temp_ref( string( "uh" ) + " oh!" );
    cout << s << endl;
}

I contend that there's no lifetime extension here, and that the output statement using reference s is using a dangling reference, with UB as a result.

And I think that this conclusion, unlike the OP's choice of example, cannot be argued solely on the basis of the standard's wording, because (it seems to me that) the standard's wording is a bit defective. That it fails to make an exception for reference types. But, I might be wrong, and if I learn that I'll update this answer to reflect that new understanding.

Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
  • In "Its initializer expression s is not a temporary: it's a reference to a local", you mean that name lookup finds a local, not the C++ concept of reference, right? – Ben Voigt Nov 22 '15 at 14:17
  • I mean that the expression does not produce a temporary. It does not itself create a temporary, and since it's the same type as the object to be initialized, it's not used to create a temporary. – Cheers and hth. - Alf Nov 22 '15 at 14:22