Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 200
6502 Avatar asked Mar 11 '16 18:03

6502


People also ask

What is a const reference?

A const reference is actually a reference to const. A reference is inherently const, so when we say const reference, it is not a reference that can not be changed, rather it's a reference to const. Once a reference is bound to refer to an object, it can not be bound to refer to another object.

Does const reference extend lifetime?

You are not questioning why const references are allowed to bind to temporaries, but merely why they extend the lifetime of those temporaries. If the lifetime of the temporary returned by bar() were not extended, then any usage of a (exemplified by the line (1)) would lead to undefined behavior.

Can const reference be modified?

But const (int&) is a reference int& that is const , meaning that the reference itself cannot be modified.

Does const reference copy?

Not just a copy; it is also a const copy. So you cannot modify it, invoke any non-const members from it, or pass it as a non-const parameter to any function. If you want a modifiable copy, lose the const decl on protos .


2 Answers

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.

like image 176
Columbo Avatar answered Sep 22 '22 06:09

Columbo


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 "cv2 T2" as follows:

  1. 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 "cv2 T2", or

    b. has a class type ...

    then ...

  2. 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 "cv2 T2", or

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

like image 40
Daniel Jour Avatar answered Sep 21 '22 06:09

Daniel Jour