20

It is known feature of C++ that a const reference extends the life time of the temporary object returned from a function, but is it acceptable to use constant reference to the member of the temporary object returned from the function?

Example:

#include <string>

std::pair<std::string, int> getPair(int n)
{
    return {std::to_string(n), n};
}

int main(int, char*[])
{
    const int x = 123456;
    const auto& str = getPair(x).first;
    printf("%d = %s\n", x, str.c_str());    
    return 0;
}

Output:

123456 = 123456
songyuanyao
  • 169,198
  • 16
  • 310
  • 405
groman
  • 303
  • 1
  • 6
  • 2
    imho phrases like "...is a well known feature.." are not so nice. They discriminate those who dont know that feature and carries no information whatsoever. Otherwise, interesting question – 463035818_is_not_an_ai Jul 06 '16 at 14:31
  • 2
    I am not 100% sure, but I think it's valid as per standard. The lifetime of the temporary should be prolonged as long as the lifetime of its member access (`str` in this case). That said, you should be just ok by taking return value by copy. `RVO` will avoid doing extra copies. – Arunmu Jul 06 '16 at 14:34
  • 1
    Highly related: http://stackoverflow.com/questions/35947296/about-binding-a-const-reference-to-a-sub-object-of-a-temporary – NathanOliver Jul 06 '16 at 15:41
  • 1
    I think that answer is out of date compared to the current standard – Smeeheey Jul 06 '16 at 16:21

4 Answers4

13

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.
Smeeheey
  • 9,906
  • 23
  • 39
  • 1
    I'm 99% sure the highlighted text refers specifically to binding a parent-class reference to a child-class temporary object. – Mark B Jul 06 '16 at 14:47
  • @MarkB Why do you think that? – Barry Jul 06 '16 at 14:52
  • @MarkB - see my update. I've included definition of subobject – Smeeheey Jul 06 '16 at 14:53
  • So what subobject is `first` a complete object of? – NathanOliver Jul 06 '16 at 14:55
  • 2
    Here, `first` is the subobject and `pair` is the complete object. The rules say that "the complete object (i.e. `pair`) of the subobject (i.e. `first`) to which the reference is bound" also has to have the lifetime-extension – Smeeheey Jul 06 '16 at 14:56
  • OK. I'll take that. – NathanOliver Jul 06 '16 at 15:01
  • I'm still not convinced, it seems you have to take the entire section 5 together, considering also `The second context is when a reference is bound to a temporary.` Note that even though they call out subobject below, it doesn't say "...to a temporary or named subobject of a temporary". So part one means you're binding a temporary directly to a reference. Your highlighted text means that the bound-to reference type may be a parent object reference type. – Mark B Jul 06 '16 at 15:16
  • What it says is that if you bind a reference to a subobject, then the complete object which owns that subobject also has to have its lifetime extended. Here `first` is the subobject and `pair` is the complete object - so that means `pair` has to have its lifetime extended too, leaving a reference to its subobject (`first`) valid. – Smeeheey Jul 06 '16 at 15:19
  • @MarkB `getPair(x).first` *is* a temporary. It's just a temporary that happens to be a subobject of another temporary. – Barry Jul 06 '16 at 15:21
  • @Barry no it is not a temporary. `getPair(x)` is. What you gave is a subobject of a temporary object. Until we have http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1299 applied at least. Then it could be marked a "temporary expression", with the "corresponding temporary object" being its complete object denoted by `getPair(x)` – Johannes Schaub - litb Jul 06 '16 at 15:22
  • A subobject of a temporary object is also a temporary object – Smeeheey Jul 06 '16 at 15:27
  • I don't think that's true: Nowhere the spec says that. For example, I don't think `return getPair(x).first` is allowed to be RVOed because it's not a temporary. – Johannes Schaub - litb Jul 06 '16 at 15:31
  • I agree your example can't be RVOed - but that's because of the constraint on copy elision as per [class.copy/31.1], which says that return copy elision can only be performed when you're returning an automatic object by name, so your example doesn't qualify on that basis. I don't think it shows that `first` is not a temporary in this case, although the rules on the exact definition of a temporary object seem to be somewhat ambigious – Smeeheey Jul 06 '16 at 15:38
  • @Smeeheey you're reading a post-C++14 draft, which has moved the RVO-allowance to section 8.6. Of course `return getPair(x)` is allowed to be RVOed, that's pretty much one of the simplest cases you can have. For reference: Everything I say is with regard to the current language. For C++17, I can't use this RVO rule because it only allows RVO for prvalues (and not on temporaries.. btw the returned expression here is an xvalue), that's why C++17 is not applicable for my *inductive* argument. My *deductive* argument is: the spec does not make `.first` a temporary. – Johannes Schaub - litb Jul 06 '16 at 15:46
  • I agree in that I haven't shown the spec to prove that `.first` is a temporary, but I don't think you've proved the converse either. Like I said, it seems to be ambiguous. – Smeeheey Jul 06 '16 at 15:52
  • And BTW, I was not referring to a post-C++14 draft. I was referring to n4296, which is described as "the C++14 standard plus minor editorial changes". And the section that I'm referring to is 12.8. – Smeeheey Jul 06 '16 at 16:01
  • @Smeeheey hooray, n4296 in 12.8 contains "when a temporary class object that has not been bound to a reference (12.2) would be copied/moved to a class object with the same cv-unqualified type, the copy/move operation can be omitted by constructing the temporary object directly into the target of the omitted copy/move". That doesn't say "can only be performed when you're returning an automatic object by name". Anyway, since you made the claim that it's a temporary object, you have the burden of proof. Just like it's my burden of proof if I would claim that `10` would be a temporary object. – Johannes Schaub - litb Jul 06 '16 at 16:07
  • After all, an object is temporary only when it was created as such. So, if you want to say of an object that it's temporary, you should be able to find the corresponding rule. – Johannes Schaub - litb Jul 06 '16 at 16:11
  • You can make an inductive argument about the sentence "The third context is when a reference is bound to a temporary.", because it would say that `auto &&x = getPair().first` binds to a temporary. However, here I think the sentence is defective, as with most of the other sentences of this kind in 12.2 (they do not mean "temporary object", but "temporary expression". Of course, `auto &&x = *this;` will not lengthen the lifetime of `*this`, even if `*this` refers to a temporary object....). – Johannes Schaub - litb Jul 06 '16 at 16:14
  • 1
    I'm not stating that the standard shows that `.first` is a temporary object. I'm saying the standard is ambiguous on the term. In fact it repeatedly refers to it without formally defining it. However, if a subobject of a temporary object is *not* a temporary object itself, then I don't see under what circumstances the highlighted part of the rule in my answer above could possibly apply, and therefore the point of said part of rule. – Smeeheey Jul 06 '16 at 16:18
  • Do the subobjects of subobjects also get their lifetime extended? – papplesharp Nov 10 '21 at 09:00
  • Yes, because they’re also subobjects by above definition – Smeeheey Nov 17 '21 at 19:20
8

It's well defined.

From the standard: $12.2/6 Temporary objects [class.temporary]:

(emphasis mine)

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

And about the subobect, $1.8/2 The C++ object model [intro.object]:

(emphasis mine)

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

first is bound to reference and it's the member subobject of std::pair, so the temporary std::pair (i.e. the complete object) 's lifetime will be prolonged, the code should be fine.

For reference only: Clang and GCC say yes, VC says no.

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
  • But is `first` the complete object of a subobject? – NathanOliver Jul 06 '16 at 14:53
  • 1
    @NathanOliver The temporary that is the complete object of a subobject... Not `first`. – songyuanyao Jul 06 '16 at 14:54
  • 2
    Looks like gcc bug. See http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html. "1834. Constant initialization binding a reference to an xvalue". I think `first` here is an xvalue. – Arunmu Jul 06 '16 at 15:02
  • 2
    GCC doesn't implement CWG 1834 yet - if you tried the same with a non-integral type (to avoid the constant collapsing), you'd see the same behavior from gcc. – Barry Jul 06 '16 at 15:19
  • @Barry and Arunmu, you're right. Glad to know that. – songyuanyao Jul 06 '16 at 15:39
2

As I mentioned in my comment:

The lifetime of the temporary should be prolonged as long as the lifetime of its member access (str in this case). That said, you should be just ok by taking return value by copy. RVO will avoid doing extra copies.

From the standard, section 12.2.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:

— A temporary bound to a reference member in a constructor’s ctor-initializer (12.6.2) persists until the constructor exits.

— A temporary bound to a reference parameter in a function call (5.2.2) persists until the completion of the full-expression containing the call.

To stay clear out of any trouble, I would rather do:

auto m_p = getPair(x);

This is as efficient as it can get because of RVO which every compiler must be doing for this case.

Arunmu
  • 6,837
  • 1
  • 24
  • 46
  • *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* says nothing about class members which is what `first` is. – NathanOliver Jul 06 '16 at 14:47
  • Isn't `first` a subobject of `pair` in this case ? – Arunmu Jul 06 '16 at 14:48
  • 1
    I am pretty sure it is talking about `const base& foo = deriveied_temporary()`. here. – NathanOliver Jul 06 '16 at 14:50
  • @NathanOliver - see [intro.object/2] (as per my answer). Here `first` is indeed a subobject. – Smeeheey Jul 06 '16 at 14:52
  • @NathanOliver Yeah, I agree with Smeeheey. That's what it seems like. Can you tell why you think otherwise ? – Arunmu Jul 06 '16 at 14:55
-1

This seems addressed in 12.2/4-5:

There are two contexts in which temporaries are destroyed at a different point than the end of the full expression. The first context... [stuff dealing with arrays]

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:

There are four exceptions dealing with constructor binding of reference members, function calls, functions returning by reference, and references bound in new-initializers.

None of these cases apply, so the temporary is destroyed at the end of the full statement, leaving a dangling reference to a member of the temporary.

Just help the compiler realize it can move from the temporary: const auto str = std::move(getPair(x).first);

Community
  • 1
  • 1
Mark B
  • 95,107
  • 10
  • 109
  • 188