Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can a const int ref in a constructor safely bind to a literal?

I know the standard has an exception about extending the lifetime of temporaries that basically says binding a const reference in a constructor won't extend the lifetime, but does this also apply to literals? For example:

class C {
    private:
        const int& ref;
    public:
        C(const int& in)
            : ref{in}
        { }
};

If I had a function returning an object of this type

C f() {
    C c(2);
    return c;
}

Would the value of c.ref be undefined in the caller if I know it's bound to a literal?

like image 624
Ryan Haining Avatar asked Jan 27 '15 17:01

Ryan Haining


2 Answers

No. You will not be able to use the reference after the constructor finishes execution.
When a prvalue of non-class type is bound to a const-reference of that same type, a temporary is always introduced. Thus the constructor's parameter reference will refer to a temporary that is destroyed once the reference goes out of scope. After that happens, the member reference is dangling, and attempting to access the stored value behind that reference results in UB. [dcl.init.ref]/5:

A reference to type “cv1 T1” is initialized by an expression of type “cv2 T2” as follows:

  • If the reference is an lvalue reference and the initializer expression
    • is an lvalue (but is not a bit-field), and [..]
    • has a class type (i.e., T2 is a class type) [..]
  • 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.

    • If the initializer expression

      • is an xvalue (but not a bit-field), class prvalue, array prvalue or function lvalue and [..]
      • has a class type [..]
    • Otherwise: (5.2.2.1)

      • If T1 is a class type [..]
      • If T1 is a non-class type, a temporary of type “cv1 T1” is created and copy-initialized (8.5) from the initializer expression. The reference is then bound to the temporary.

And integer literals are, unsurprisingly, indeed prvalues. [expr.prim.general]/1:

A string literal is an lvalue; all other literals are prvalues.

Finally, in case this is unclear, [class.temporary]/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:

  • A temporary bound to a reference member in a constructor’s ctor-initializer (12.6.2) persists until the constructor exits.
like image 200
Columbo Avatar answered Oct 04 '22 00:10

Columbo


Short answer: Evaluating c.ref will almost certainly be illegal (invoke undefined behavior).

Long answer: When binding a reference to an integer literal, what you are really doing is the following:

The integer literal refers to what is known as "a value that is not associated with an object".

To bind a reference to it, an object needs to be created that holds the same value. The reason for that is that a reference (or pointer) must always point to an object (which in turn is nothing more than a bit of memory). Therefore, a temporary object is created which holds the value.

Temporary objects are guaranteed to last as long as the expression they are created by is being evaluated. Since your object exists for longer, the temporary object that held your value is being destroyed early and the reference may not be accessed anymore.

Note that if you access c.ref within the expression that created c, you would actually be fine.

like image 39
gha.st Avatar answered Oct 04 '22 00:10

gha.st