4

C++

I'm trying to see how const references prolong the lifetime of temporaries. I'm running the code from the snippet in one of the answers to What are the differences between pointer variable and reference variable in C++? and got conflicting results between VC11 and g++ 4.8. I've expanded the snippet here:

#include <stdio.h>

struct scope_test
{
    ~scope_test() { printf("scope_test done!\n"); }
};

int main()
{
    const scope_test& test = scope_test();
    printf("in scope\n");
}

The answerer got the result:

in scope
scope_test done!

I tried it in VC11 and got this:

scope_test done!
in scope
scope_test done!

I assumed the VC11 result was caused by a lack of copy elision, so I tried to see if disabling copy elision on g++ with fno-elide-constructors would give the same result as VC11. (I don't think copy elision can be toggled in VC11.) But, g++ gives the answerer's result regardless of the setting of the flag.

The C++11 Standard, ISO/IEC 14882:2011(E), §12.2/4 and /5 states:

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:

...

Does the VC11 result have anything to do with copy elision? Is it a VC11 bug?

The answerer states:

temporaries assigned to const references live until the const reference goes out of scope

The list of exceptions to §12.2/5 don't rule out a non-const reference. What am I missing from the Standard?

Removing the const in VC11 produces the same result as the VC11 one with the const. Removing the const in g++ gives error: invalid initialization of non-const reference of type ‘scope_test&’ from an rvalue of type ‘scope_test’. Why is there a difference?

EDIT:

I added copy and move constructors and tried:

#include <stdio.h>

struct scope_test
{
    scope_test() { printf("regular ctor\n"); }
    scope_test(const scope_test& src) { printf("copy ctor\n"); }
    scope_test(scope_test&& src) { printf("move ctor\n"); }
    ~scope_test() { printf("scope_test done!\n"); }
};

int main()
{
    const scope_test& test= scope_test();
    printf("in scope\n");
}

Regardless of the toggling of copy elision, g++ gives:

regular ctor
in scope
scope_test done!

VC11 gives the same thing, even if the const is removed. If the const is removed from g++, g++ still gives error: invalid initialization of non-const reference of type ‘scope_test&’ from an rvalue of type ‘scope_test’.

R Sahu
  • 204,454
  • 14
  • 159
  • 270
CodeBricks
  • 1,771
  • 3
  • 17
  • 37
  • There isn't any copying, though, since there is only *one* object. – Kerrek SB Oct 10 '13 at 22:06
  • The `const` has nothing to do with the lifetime prolongation. Any reference will do. It's just that non-const lvalue references can't bind to rvalues, so the can never be used that way. – Kerrek SB Oct 10 '13 at 22:07
  • @KerrekSB: Binding a temporary to a `const` reference can cause a copy construction. The easiest approach to verify whether there is a copy created is to instrument the copy constructor. – Dietmar Kühl Oct 10 '13 at 22:07
  • @DietmarKühl: This isn't a function returning a value, though, it's literally an object on the RHS. – Kerrek SB Oct 10 '13 at 22:14
  • If there is a public answer to this it is here: http://blogs.msdn.com/b/vcblog/archive/2011/09/12/10209291.aspx I didn't go through this, but knowing how slow Microsoft is on implementing new C++ standard, it may be just that it's the VC. – tomi.lee.jones Oct 10 '13 at 22:15
  • 1
    @KerrekSB: Yes, but the words don't mandate binding to the same object just to an object with the same value, i.e., potentially a copy 8.5.3 [dcl.init.ref] paragraph 5: "If the initializer expression ... is an xvalue ... then the reference is bound to the value of the initializer expression ...". C++03 more explicitly allowed the copy: "If the initializer expression is an rvalue ... the reference is bound in one of the following ways (the choice is implementation- defined): ... and a constructor is called to copy the entire rvalue object into the temporary." (omission due to limited space). – Dietmar Kühl Oct 10 '13 at 22:23
  • @DietmarKühl: very interesting. Sounds like copy "unlision"... – Kerrek SB Oct 10 '13 at 22:27
  • @KerrekSB : re: `The const has nothing to do with the lifetime prolongation...It's just that non-const lvalue references can't bind to rvalues, so the can never be used that way.` According to DietmarKühl's answer: `Otherwise, the reference shall be to a non-volatile const type...`, the Standard's wording doesn't state the reference must be declared const; it states the referent shall be a const type. The non-const form compiles in VC11. Am I missing something here? – CodeBricks Oct 11 '13 at 22:04
  • @CodeBricks: The reference can be `T&&` and it will extend the lifetime. – Kerrek SB Oct 11 '13 at 22:09
  • @CodeBricks: Re Dietmar's explanations: I think the main interest in being able to make copies is if you say something like `const T & x = foo()`, with `T foo();`. In that case, you definitely want to be able to copy the result of the function call into the local scope. But when you have `T const & x = T()`, the object arguably *is already* in the local scope, so I would *hope* that no copying was needed, though the standard seems to allow it anyway (perhaps so as to just not to have to make extra rules about the differences). Just my opinion... – Kerrek SB Oct 11 '13 at 22:12
  • @KerrekSB : So is `scope_test& test= scope_test();` ill-formed and therefore a VC11 bug, since it compiles in VC11, but not in g++? For that matter `scope_test test = *const_cast( &( (scope_test()) ) );` also compiles in VC11, but not in g++; g++ states `error: taking address of temporary` – CodeBricks Oct 11 '13 at 22:56

1 Answers1

2

Both behaviors are correct, certainly according to the C++03 standard (8.5.3 [dcl.init.ref] paragraph 5):

Otherwise, the reference shall be to a non-volatile const type (i.e., cv1 shall be const). [Example: ...]

  • If the initializer expression is an rvalue, with T2 a class type, and “cv1 T1” is reference-compatible with “cv2 T2,” the reference is bound in one of the following ways (the choice is implementation-defined):

    — 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.

I think the definition of C++11 still allows the copy to be made but the wording doesn't as clearly allow the copy. In any case, VC++ doesn't claim to be fully C++11 compliant.

Community
  • 1
  • 1
Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
  • By "Both behaviors are correct", do you mean both VC11 and g++ output are correct for the example with const reference or for the example with the non-const reference? Also, I added copy and move ctors (see EDIT). If the VC11 example is a case of `— 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...`; does that mean VC11 elides here, since it doesn't output `copy ctor` or `move ctor`, but not when I don't define my own copy and move ctors? – CodeBricks Oct 11 '13 at 03:06