const reference to member of temporary object [duplicate]
Asked Answered
F

4

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
Fadge answered 6/7, 2016 at 14:22 Comment(4)
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 questionAnnalee
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.Mayle
Highly related: #35947796Syncretize
I think that answer is out of date compared to the current standardHallway
H
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.
Hallway answered 6/7, 2016 at 14:46 Comment(22)
I'm 99% sure the highlighted text refers specifically to binding a parent-class reference to a child-class temporary object.Epner
@MarkB Why do you think that?Pomelo
@MarkB - see my update. I've included definition of subobjectHallway
So what subobject is first a complete object of?Syncretize
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-extensionHallway
OK. I'll take that.Syncretize
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.Epner
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.Hallway
@MarkB getPair(x).first is a temporary. It's just a temporary that happens to be a subobject of another temporary.Pomelo
@Pomelo no it is not a temporary. getPair(x) is. What you gave is a subobject of a temporary object. Until we have 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)Delorenzo
A subobject of a temporary object is also a temporary objectHallway
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.Delorenzo
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 ambigiousHallway
@Hallway 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.Delorenzo
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.Hallway
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.Hallway
@Hallway 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.Delorenzo
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.Delorenzo
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....).Delorenzo
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.Hallway
Do the subobjects of subobjects also get their lifetime extended?Vinery
Yes, because they’re also subobjects by above definitionHallway
I
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.

Il answered 6/7, 2016 at 14:51 Comment(5)
But is first the complete object of a subobject?Syncretize
@Syncretize The temporary that is the complete object of a subobject... Not first.Il
Looks like gcc bug. See 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.Mayle
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.Pomelo
@Pomelo and Arunmu, you're right. Glad to know that.Il
M
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.

Mayle answered 6/7, 2016 at 14:45 Comment(5)
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.Syncretize
Isn't first a subobject of pair in this case ?Mayle
I am pretty sure it is talking about const base& foo = deriveied_temporary(). here.Syncretize
@Syncretize - see [intro.object/2] (as per my answer). Here first is indeed a subobject.Hallway
@Syncretize Yeah, I agree with Smeeheey. That's what it seems like. Can you tell why you think otherwise ?Mayle
E
-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);

Epner answered 6/7, 2016 at 14:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.