Does "T const&t = C().a;" lengthen the lifetime of "a"?
Asked Answered
H

4

20

The following scenario is given, to be interpreted as C++0x code:

struct B { }; 
struct A { B b; }; 
int main() { 
  B const& b = A().b; 
  /* is the object still alive here? */
}

Clang and GCC (trunk version as of 2011/02) behave differently: Clang lengthens the lifetime. GCC moves B to a new temporary object, and then binds the reference to that new temporary.

I cannot find either behavior can be derived from the words of the Standard. The expression A().b is not a temporary (see 5.2.5). Can anyone please explain the following to me?

  • Desired behavior (the intent of the committee)
  • The behavior as you derive it from the FDIS

Thanks!

Heres answered 16/4, 2011 at 20:37 Comment(2)
How do you deduce that A().b isn't a temporary from 5.2.5?Thibaut
@Thibaut because it doesn't say so. I haven't found anything else to do so. As I would expect 5.2.5 do so (compare with open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#462), I mentioned 5.2.5 which defines the meaning of a.b.Heres
L
11

In 12.2 paragraph 5 of N3126=10-0116 it's said that:

The second context [ in which temporaries are destroyed at a different point than the end of the full-expression ] 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 ...

and then follows a list of four special cases (ctor-inizializers, reference parameters, returned value, new initializer).

So (in this version) seems to me that clang is correct because you're binding the reference to a subobject of a temporary.

EDIT

Thinking to the base sub-object of an object this also seems to be the only reasonable behavior. The alternative would mean doing a slicing in:

Derived foo();
...
void bar()
{
    Base& x = foo(); // not very different from foo().b;
    ...
}

Actually after making a little experiment seems indeed that g++ differentiates between a member sub-object and a base sub-object, but I don't understand where this differentiation is made in the standard. The following is the test program I used and where it's clearly visible the different handling of the two cases... (B is Base, D is Derived and C is composed).

#include <iostream>

struct B
{
    B()
    { std::cout << "B{" << this << "}::B()\n"; }

    B(const B& x)
    { std::cout << "B{" << this << "}::B(const B& " << &x << ")\n"; }

    virtual ~B()
    { std::cout << "B{" << this << "}::~B()\n"; }

    virtual void doit() const
    { std::cout << "B{" << this << "}::doit()\n"; }
};

struct D : B
{
    D()
    { std::cout << "D{" << this << "}::D()\n"; }

    D(const D& x)
    { std::cout << "D{" << this << "}::D(const D& " << &x << ")\n"; }

    virtual ~D()
    { std::cout << "D{" << this << "}::~D()\n"; }

    virtual void doit() const
    { std::cout << "D{" << this << "}::doit()\n"; }
};

struct C
{
    B b;

    C()
    { std::cout << "C{" << this << "}::C()\n"; }

    C(const C& x)
    { std::cout << "C{" << this << "}::C(const C& " << &x << ")\n"; }

    ~C()
    { std::cout << "C{" << this << "}::~C()\n"; }
};

D foo()
{
    return D();
}

void bar()
{
    std::cout << "Before calling foo()\n";
    const B& b = foo();
    std::cout << "After calling foo()\n";
    b.doit();
    std::cout << "After calling b.doit()\n";

    const B& b2 = C().b;
    std::cout << "After binding to .b\n";
    b2.doit();
    std::cout << "After calling b2.doit()\n";
}

int main()
{
    std::cout << "Before calling bar()\n";
    bar();
    std::cout << "After calling bar()\n";
    return 0;
}

The output I get with g++ (Ubuntu/Linaro 4.4.4-14ubuntu5) 4.4.5 is

Before calling bar()
Before calling foo()
B{0xbf9f86ec}::B()
D{0xbf9f86ec}::D()
After calling foo()
D{0xbf9f86ec}::doit()
After calling b.doit()
B{0xbf9f86e8}::B()
C{0xbf9f86e8}::C()
B{0xbf9f86e4}::B(const B& 0xbf9f86e8)
C{0xbf9f86e8}::~C()
B{0xbf9f86e8}::~B()
After binding to .b
B{0xbf9f86e4}::doit()
After calling b2.doit()
B{0xbf9f86e4}::~B()
D{0xbf9f86ec}::~D()
B{0xbf9f86ec}::~B()
After calling bar()

In my opinion this is either a bug in g++ or a bug in what the c++ standard mandates if this is really the expected behavior or a possible acceptable behavior (but I must tell that I didn't really think about it a lot, this is just a feeling that something is wrong with this differentiation).

Leiker answered 16/4, 2011 at 21:2 Comment(12)
Ah this indicates that indeed the desired result is that the lifetime is extended. Good find! However, it requires that A().b is a temporary expression so that we end up applying that paragraph ("when a reference is bound to a temporary"). I've not found the spec saying so.Heres
The document says also or the temporary that is the complete object of a subobject to which the reference is bound. You are binding a reference to a subobject b of a temporary A().Leiker
@Leiker that "or the temporary that is..." appertains to "persists for the lifetime of the reference", not to "when a reference is bound to a temporary". So the whole text is still under the condition that we bind a reference to a temporary (first sentence of the paragraph), I think.Heres
Your point is that a subobject of a temporary is not a temporary? In 3.7.5 it's said that The storage duration of member subobjects, base class subobjects and array elements is that of their complete object... I think that no genuine doubt exist that a subobject of a temporary is temporary.Leiker
Even if a subobject of a temporary is a temporary object, that doesn't mean that the expression A().b is a temporary. For example: struct A { A &f() { return *this; } }; We can call A().f().f(). The lifetime of the temporary object is not ended at the return statement, even though "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.". And I think the reason is that *this is not a temporary expression.Heres
Temporary is a compile-time concept, not a run-time concept. Inside the body of a function void foo(const A& a){...} surely a is not a temporary, but clearly if I call foo(A()) I am passing a temporary: the object is a temporary in the context of the caller, and the very same run-time object is not a temporary in the context of the called function. A() is a temporary, A().b is a sub-object of a temporary and you are binding a reference to this sub-object and this extends the lifetime of the full temporary object to match the lifetime of the reference. See edit.Leiker
@6502, "surely a is not a temporary". That's not what the standard says. For example, the standard allows reading from a glvalue within a constant expression, as long as it refers to a temporary object initialized from a constant expression (i.e const int &a = 0;, then reading from a is an integer constant expression). Note that the object is a temporary here. What you describe requires that an object can be a temporary at one point, and later when access by a different expression not be a temporary anymore. Nothing in the spec describes such a transformation, afaics.Heres
@Johannes Schaub: I think you're confusing the COMPILE-TIME level and the RUN-TIME level. Temporaries are a COMPILE-TIME concept... if you have a function with a reference parameter like void foo(A& a) { ... } that is a reference bound to a non-temporary object... the reason is that temporary objects come into existence for very specific reasons (e.g. calling a function, type conversion...) and with a this is not the case. The code in the function will never destroy the object. What is temporary and what is not is decided at COMPILE TIME, without knowing who will call the function.Leiker
@6502, "and with a this is not the case". True, referring to a does not create a temporary, since the temporary was already created -> the temporary that 'a' refers to comes into existence because of reference binding. 12.2 says "Temporaries of class type are created in various contexts: binding a reference to a prvalue (8.5.3) ...", and 8.5.3 says in last bullet "Otherwise, a temporary of type 'cv1 T1' is created and initialized ...".Heres
@Johannes Schaub: seems to me you're still missing my point. In the function foo, a is a reference and that's all. The compiler must generate the code for foo without knowing if it will be called with a temporary or not. A temporary is a temporary only for the code that creates it... it's a property of the compiled code, not of the object created at runtime. On the other side A() is a temporary object in your example, and A().b is therefore a subobject of a temporary object and binding a reference to it extends the life of the temporary. This is a decision taken at COMPILE TIME.Leiker
"A temporary is a temporary only for the code that creates it", I would agree with you, but the Standard would not agree with us. As I said above, int const&a = 1; int x[a]; is valid, because a refers to a temporary initialized by a constant expression. This demonstrates: The referred-to object is regarded as a temporary even for code that did not create or necessary the temporary.Heres
That is just another case where the lifetime of a temporary has been extended because the object is bound to a reference. It's not really different from A foo(){ return A(); } void bar(){ const A& a=foo(); a.do_something(); }.Leiker
F
1

Okay, I'm doing a 180 degrees on this

After refreshing my knowledge of the standard, I have to admit that it is probably right to expect the object referred to by b to remain alive (be extended) for the duration of scope in which the const& was initialized. I found GotW #88 a helpful source for this.

I fail to see how A().b is structurally or semantically different from

string f() { return "abc"; } // ABC initializes return-value **TEMP**

void g() {
const string& s = f();  // initializes with reference to a temp
  cout << s << endl;    // '*&s' is extended per standard
}

Sorry for any confusion I might have caused. I was a little out of my depth there.

Footmark answered 16/4, 2011 at 21:41 Comment(1)
f() is a temporary by 12.2. A().b is not covered by it (note that A().b is directly bound by references. no temporary is created for them by 8.5.3).Heres
T
0

Temporary objects are distinguished by the circumstances of their creation. (§12.2 "Temporaries of class type are created in various contexts…")

For temporaries created by a reference declarator, §12.2 refers us to §8.5. C++03 and C++11 differ greatly in §8.5.3, but both clearly support your code.

C++03 says that either

— The reference is bound to the object represented by the rvalue (see 3.10) or to a sub-object within that object.

— A temporary of type “cv1 T2” [sic] is created, and a constructor is called to copy the entire rvalue object into the temporary. The reference is bound to the temporary or to a sub-object within the temporary.

The discussion is entirely in terms of subobjects, not distinguishing base classes from members. So, if binding a reference to a member is disallowed, then so is binding a member to a base, which rules out ScopeGuard.

C++11 is more verbose, but specifies

— 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, class prvalue, array prvalue or function lvalue and “cv1 T1” is reference- compatible with “cv2 T2” … then the reference is bound to the value of the initializer expression."

Combined with 6502's answer, and the pointlessness of binding a reference to a value which ends at the semicolon, it is apparent that C++11 continues to support this behavior.

Tuba answered 16/4, 2011 at 21:18 Comment(0)
M
0

Let's see (all references are to the FDIS):

struct B { }; 
struct A { B b; }; 
int main() { 
  B const& b = A().b; 
}

1) 5.2.3/2 says A() is a prvalue.

2) 5.2.5/4 says that A().b is a prvalue because of point 1).

3) 8.5.3/5 says that B const& b binds directly to A().b without creating a temporary.

4) 12.2/5 says that the lifetime of a temporary bound to a reference is extended.

So it appears at least that GCC is wrong here.

Whether Clang is correct or if this is UB depends on whether the subobject of a temporary is itself a temporary. I'm quite sure the answer should be affirmative, but the Standard seems silent about the matter. Should someone submit a DR?

EDIT: As @6502 said, 3.7.5 indicates that the lifetime of a subobject is the lifetime of its complete object.

Musetta answered 16/4, 2011 at 22:21 Comment(1)
I've submitted a DR. Let's see what they say.Heres

© 2022 - 2024 — McMap. All rights reserved.