About binding a const reference to a sub-object of a temporary
With code like
#include <stdio.h>
struct P2d {
double x, y;
P2d(double x, double y) : x(x), y(y) {}
~P2d() { printf("Destructor called\n"); }
};
P2d center() {
return P2d(10, 10);
}
int main(int argc, const char *argv[]) {
const double& x = center().x;
printf("x = %.18g\n", x);
return 0;
}
g++
(version 5.2.0) will destroy the P2d
temporary instance before entering the printf
in main
, but the value will be preserved anyway (i.e. instead of binding x
to the actual member of the temporary P2d
instance it will create another temporary double
to copy the value of the member).
clang++
(IMO correctly) instead extends the lifetime of the temporary P2d
instance to the lifetime of the x
reference and the destructor will be therefore called after the printf
in main
.
If instead of using plain double
as type for the x
and y
members you make a class (e.g. Double
) then both compilers agree and they extend the lifetime of the temporary P2d
object to past the printf
.
Is this a bug in g++
or something allowed by the standard?
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 andE2
does not designate a bit-field, thenE1.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.
I would argue for a bug in g++, because, quoting draft N3242, §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:
So its lifetime must be extended, except when:
A temporary bound to a reference member in a constructor’s ctor-initializer [..]
A temporary bound to a reference parameter in a function call [..]
The lifetime of a temporary bound to the returned value in a function return statement [..]
A temporary bound to a reference in a
new-initializer
[..]
Our case doesn't fit any of these exceptions, thus it must follow the rule. I'd say g++ is wrong here.
Then, regarding the quote aschepler brought up from the same draft §8.5.3/5 (emphasis mine):
A reference to type "cv1
T1
" is initialized by an expression of type "cv2T2
" as follows:
If the reference is an lvalue reference and the initializer expression
a. is an lvalue (but is not a bit-field) and "cv1
T1
" is reference-compatible with "cv2T2
", orb. has a class type ...
then ...
Otherwise, the reference shall be an lvalue reference to a non-volatile const type (i.e., cv1 shall be
const
), or the reference shall be an rvalue reference.a. If the initializer expression
i. is an xvalue, class prvalue, array prvalue or function lvalue and "cv1
T1
" is reference-compatible with "cv2T2
", orii. has a class type ...
then the reference is bound to the value of the initializer expression in the first case....
b. Otherwise, a temporary of type "cv1
T1
" is created and initialized from the initializer expression using the rules for a non-reference copy-initialization (8.5). The reference is then bound to the temporary.
Looking at what an xvalue is, this time quoting http://en.cppreference.com/w/cpp/language/value_category ...
An xvalue ("expiring value") expression is [..]
a.m
, the member of object expression, where a is an rvalue and m is a non-static data member of non-reference type;
... the expression center().x
should be an xvalue, thus case 2a from §8.5.3/5 applies (and not the copy). I'll stay with my suggestion: g++ is wrong.
Just read Columbo's answer.
This is a gcc bug. The relevant rule is in [class.temporary]:
There are two contexts in which temporaries are destroyed at a different point than the end of the full-expression. [...]
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 object bound to a reference parameter in a function call (5.2.2) 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 (6.6.3) is not extended; the temporary is destroyed at the end of the full-expression in the return statement.
— A temporary bound to a reference in a new-initializer (5.3.4) persists until the completion of the full-expression containing the new-initializer.
We're binding a reference to a subobject of a temporary, so the temporary should persist for the lifetime of the reference. None of those three exceptions to this rule apply here.