GCC's implementation destroys a std::initializer_list array returned from a function at the end of the return full-expression. Is this correct?
Both test cases in this program show the destructors executing before the value can be used:
#include <initializer_list>
#include <iostream>
struct noisydt {
~noisydt() { std::cout << "destroyed\n"; }
};
void receive( std::initializer_list< noisydt > il ) {
std::cout << "received\n";
}
std::initializer_list< noisydt > send() {
return { {}, {}, {} };
}
int main() {
receive( send() );
std::initializer_list< noisydt > && il = send();
receive( il );
}
I think the program should work. But the underlying standardese is a bit convoluted.
The return statement initializes a return value object as if it were declared
std::initializer_list< noisydt > ret = { {},{},{} };
This initializes one temporary initializer_list and its underlying array storage from the given series of initializers, then initializes another initializer_list from the first one. What is the array's lifetime? "The lifetime of the array is the same as that of the initializer_list object." But there are two of those; which one is ambiguous. The example in 8.5.4/6, if it works as advertised, should resolve the ambiguity that the array has the lifetime of the copied-to object. Then the return value's array should also survive into the calling function, and it should be possible to preserve it by binding it to a named reference.
On LWS, GCC erroneously kills the array before returning, but it preserves a named initializer_list per the example. Clang also processes the example correctly, but objects in the list are never destroyed; this would cause a memory leak. ICC doesn't support initializer_list at all.
Is my analysis correct?
C++11 §6.6.3/2:
A return statement with a braced-init-list initializes the object or reference to be returned from the function by copy-list-initialization (8.5.4) from the specified initializer list.
8.5.4/1:
… list-initialization in a copy-initialization context is called copy-list-initialization.
8.5/14:
The initialization that occurs in the form T x = a; … is called copy-initialization.
Back to 8.5.4/3:
List-initialization of an object or reference of type T is defined as follows: …
— Otherwise, if T is a specialization of std::initializer_list<E>, an initializer_list object is constructed as described below and used to initialize the object according to the rules for initialization of an object from a class of the same type (8.5).
8.5.4/5:
An object of type std::initializer_list<E> is constructed from an initializer list as if the implementation allocated an array of N elements of type E, where N is the number of elements in the initializer list. Each element of that array is copy-initialized with the corresponding element of the initializer list, and the std::initializer_list<E> object is constructed to refer to that array. If a narrowing conversion is required to initialize any of the elements, the program is ill-formed.
8.5.4/6:
The lifetime of the array is the same as that of the initializer_list object. [Example:
typedef std::complex<double> cmplx;
std::vector<cmplx> v1 = { 1, 2, 3 };
void f() {
std::vector<cmplx> v2{ 1, 2, 3 };
std::initializer_list<int> i3 = { 1, 2, 3 };
}
For v1 and v2, the initializer_list object and array createdfor { 1, 2, 3 } have full-expression lifetime. For i3, the initializer_list object and array have automatic lifetime. — end example]
A little clarification about returning a braced-init-list
When you return a bare list enclosed in braces,
A return statement with a braced-init-list initializes the object or reference to be returned from the function by copy-list-initialization (8.5.4) from the specified initializer list.
This doesn't imply that the object returned to the calling scope is copied from something. For example, this is valid:
struct nocopy {
nocopy( int );
nocopy( nocopy const & ) = delete;
nocopy( nocopy && ) = delete;
};
nocopy f() {
return { 3 };
}
this is not:
nocopy f() {
return nocopy{ 3 };
}
Copy-list-initialization simply means the equivalent of the syntax nocopy X = { 3 } is used to initialize the object representing the return value. This doesn't invoke a copy, and it happens to be identical to the 8.5.4/6 example of an array's lifetime being extended.
And Clang and GCC do agree on this point.
Other notes
A review of N2640 doesn't turn up any mention of this corner case. There has been extensive discussion about the individual features combined here, but I don't see anything about their interaction.
Implementing this gets hairy as it comes down to returning an optional, variable-length array by value. Because the std::initializer_list doesn't own its contents, the function has to also return something else which does. When passing to a function, this is simply a local, fixed-size array. But in the other direction, the VLA needs to be returned on the stack, along with the std::initializer_list's pointers. Then the caller needs to be told whether to dispose of the sequence (whether they're on the stack or not).
The issue is very easy to stumble upon by returning a braced-init-list from a lambda function, as a "natural" way to return a few temporary objects without caring how they're contained.
auto && il = []() -> std::initializer_list< noisydt >
{ return { noisydt{}, noisydt{} }; }();
Indeed, this is similar to how I arrived here. But, it would be an error to leave out the -> trailing-return-type because lambda return type deduction only occurs when an expression is returned, and a braced-init-list is not an expression.
std::initializer_list is not a container, don't use it to pass values around and expect them to persist
DR 1290 changed the wording, you should also be aware of 1565 and 1599 which aren't ready yet.
Then the return value's array should also survive into the calling function, and it should be possible to preserve it by binding it to a named reference.
No, that doesn't follow. The array's lifetime doesn't keep being extended along with the initializer_list. Consider:
struct A {
const int& ref;
A(const int& i = 0) : ref(i) { }
};
The reference i binds to the temporary int, and then the reference ref binds to it as well, but that doesn't extend the lifetime of i, it still goes out of scope at the end of the constructor, leaving a dangling reference. You don't extend the underlying temporary's lifetime by binding another reference to it.
Your code might be safer if 1565 is approved and you make il a copy not a reference, but that issue is still open and doesn't even have proposed wording, let alone implementation experience.
Even if your example is meant to work, the wording regarding lifetime of the underlying array is obviously still being improved and it will take a while for compilers to implement whatever final semantics are settled on.
The wording you refer to in 8.5.4/6 is defective, and was corrected (somewhat) by DR1290. Instead of saying:
The lifetime of the array is the same as that of the initializer_list object.
... the amended standard now says:
The array has the same lifetime as any other temporary object (12.2 [class.temporary]), except that initializing an initializer_list object from the array extends the lifetime of the array exactly like binding a reference to a temporary.
Therefore the controlling wording for the lifetime of the temporary array is 12.2/5, which says:
The lifetime of a temporary bound to the returned value in a function return statement is not extended; the temporary is destroyed at the end of the full-expression in the return statement
Therefore the noisydt objects are destroyed before the function returns.
Until recently, Clang had a bug that caused it to fail to destroy the underlying array for an initializer_list object in some circumstances. I've fixed that for Clang 3.4; the output for your test case from Clang trunk is:
destroyed
destroyed
destroyed
received
destroyed
destroyed
destroyed
received
... which is correct, per DR1290.
Related
Consider the following code:
struct Temp{ int i = 0; };
Temp GetTemp() { return Temp{}; }
int main()
{
for (struct{Temp const & t; int counter;} v = {GetTemp(), 0};
v.counter < 10;
++v.counter)
{
// Is v.t guaranteed to not be a dangling reference?
std::cout << v.t.i << std::endl;
}
}
So GetTemp() returns a temporary object, which then gets assigned to a constant reference variable. However, that constant reference variable is a member of an anonymous local struct. Question: Does the C++ standard guarantee that the lifetime of that temporary gets extended till after the loop terminates?
Considering this question, I would have expected the answer to be no, i.e. that I get a dangling reference in the loop body. However, gcc and clang seem to extend the lifetime (see example on godbolt), and even do not complain with -fsanitize=undefined, which surprised me.
For braced aggregate initialization as in your example, lifetime extension has been guaranteed since C++98 (irrespective of the linkage/visibility properties of the class). This is intuitive, since the reference is directly bound to the temporary, and not via some intermediate ctor parameter, as in the question you've linked. For legalese, see [class.temporary] in the C++14 FD that outlines the lifetime extension contexts.
See also the note here which differentiates braced and parenthetical initialization since C++20:
[Note 7: By contrast with direct-list-initialization, narrowing
conversions ([dcl.init.list]) are permitted, designators are not
permitted, a temporary object bound to a reference does not have its
lifetime extended ([class.temporary]), and there is no brace elision.
[Example 3:
struct A {
int a;
int&& r;
};
int f();
int n = 10;
A a1{1, f()}; // OK, lifetime is extended
A a2(1, f()); // well-formed, but dangling reference
Does a constant reference member variable in an anonymous struct extend the lifetime of a temporary?
Yes, the chain of const references is unbroken. There's is only v and v is alive until the end of the for loop and the lifetime of the referenced Temp is therefore extended until then. The fact that the struct is anonymous has no impact.
class.temporary/4
There are two contexts in which temporaries are destroyed at a different point than the end of the full-expression. The first context is when a default constructor is called to initialize an element of an array. If the constructor has one or more default arguments, the destruction of every temporary created in a default argument is sequenced before the construction of the next array element, if any.
class.temporary/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:
(5.1) A temporary bound to a reference member in a constructor's ctor-initializer ([class.base.init]) persists until the constructor exits.
(5.2) A temporary bound to a reference parameter in a function call ([expr.call]) persists until the completion of the full-expression containing the call.
(5.3) The lifetime of a temporary bound to the returned value in a function return statement ([stmt.return]) is not extended; the temporary is destroyed at the end of the full-expression in the return statement.
(5.4) A temporary bound to a reference in a new-initializer ([expr.new]) persists until the completion of the full-expression containing the new-initializer.
None of the exceptions to the lifetime extension applies to your case.
GCC's implementation destroys a std::initializer_list array returned from a function at the end of the return full-expression. Is this correct?
Both test cases in this program show the destructors executing before the value can be used:
#include <initializer_list>
#include <iostream>
struct noisydt {
~noisydt() { std::cout << "destroyed\n"; }
};
void receive( std::initializer_list< noisydt > il ) {
std::cout << "received\n";
}
std::initializer_list< noisydt > send() {
return { {}, {}, {} };
}
int main() {
receive( send() );
std::initializer_list< noisydt > && il = send();
receive( il );
}
I think the program should work. But the underlying standardese is a bit convoluted.
The return statement initializes a return value object as if it were declared
std::initializer_list< noisydt > ret = { {},{},{} };
This initializes one temporary initializer_list and its underlying array storage from the given series of initializers, then initializes another initializer_list from the first one. What is the array's lifetime? "The lifetime of the array is the same as that of the initializer_list object." But there are two of those; which one is ambiguous. The example in 8.5.4/6, if it works as advertised, should resolve the ambiguity that the array has the lifetime of the copied-to object. Then the return value's array should also survive into the calling function, and it should be possible to preserve it by binding it to a named reference.
On LWS, GCC erroneously kills the array before returning, but it preserves a named initializer_list per the example. Clang also processes the example correctly, but objects in the list are never destroyed; this would cause a memory leak. ICC doesn't support initializer_list at all.
Is my analysis correct?
C++11 §6.6.3/2:
A return statement with a braced-init-list initializes the object or reference to be returned from the function by copy-list-initialization (8.5.4) from the specified initializer list.
8.5.4/1:
… list-initialization in a copy-initialization context is called copy-list-initialization.
8.5/14:
The initialization that occurs in the form T x = a; … is called copy-initialization.
Back to 8.5.4/3:
List-initialization of an object or reference of type T is defined as follows: …
— Otherwise, if T is a specialization of std::initializer_list<E>, an initializer_list object is constructed as described below and used to initialize the object according to the rules for initialization of an object from a class of the same type (8.5).
8.5.4/5:
An object of type std::initializer_list<E> is constructed from an initializer list as if the implementation allocated an array of N elements of type E, where N is the number of elements in the initializer list. Each element of that array is copy-initialized with the corresponding element of the initializer list, and the std::initializer_list<E> object is constructed to refer to that array. If a narrowing conversion is required to initialize any of the elements, the program is ill-formed.
8.5.4/6:
The lifetime of the array is the same as that of the initializer_list object. [Example:
typedef std::complex<double> cmplx;
std::vector<cmplx> v1 = { 1, 2, 3 };
void f() {
std::vector<cmplx> v2{ 1, 2, 3 };
std::initializer_list<int> i3 = { 1, 2, 3 };
}
For v1 and v2, the initializer_list object and array createdfor { 1, 2, 3 } have full-expression lifetime. For i3, the initializer_list object and array have automatic lifetime. — end example]
A little clarification about returning a braced-init-list
When you return a bare list enclosed in braces,
A return statement with a braced-init-list initializes the object or reference to be returned from the function by copy-list-initialization (8.5.4) from the specified initializer list.
This doesn't imply that the object returned to the calling scope is copied from something. For example, this is valid:
struct nocopy {
nocopy( int );
nocopy( nocopy const & ) = delete;
nocopy( nocopy && ) = delete;
};
nocopy f() {
return { 3 };
}
this is not:
nocopy f() {
return nocopy{ 3 };
}
Copy-list-initialization simply means the equivalent of the syntax nocopy X = { 3 } is used to initialize the object representing the return value. This doesn't invoke a copy, and it happens to be identical to the 8.5.4/6 example of an array's lifetime being extended.
And Clang and GCC do agree on this point.
Other notes
A review of N2640 doesn't turn up any mention of this corner case. There has been extensive discussion about the individual features combined here, but I don't see anything about their interaction.
Implementing this gets hairy as it comes down to returning an optional, variable-length array by value. Because the std::initializer_list doesn't own its contents, the function has to also return something else which does. When passing to a function, this is simply a local, fixed-size array. But in the other direction, the VLA needs to be returned on the stack, along with the std::initializer_list's pointers. Then the caller needs to be told whether to dispose of the sequence (whether they're on the stack or not).
The issue is very easy to stumble upon by returning a braced-init-list from a lambda function, as a "natural" way to return a few temporary objects without caring how they're contained.
auto && il = []() -> std::initializer_list< noisydt >
{ return { noisydt{}, noisydt{} }; }();
Indeed, this is similar to how I arrived here. But, it would be an error to leave out the -> trailing-return-type because lambda return type deduction only occurs when an expression is returned, and a braced-init-list is not an expression.
std::initializer_list is not a container, don't use it to pass values around and expect them to persist
DR 1290 changed the wording, you should also be aware of 1565 and 1599 which aren't ready yet.
Then the return value's array should also survive into the calling function, and it should be possible to preserve it by binding it to a named reference.
No, that doesn't follow. The array's lifetime doesn't keep being extended along with the initializer_list. Consider:
struct A {
const int& ref;
A(const int& i = 0) : ref(i) { }
};
The reference i binds to the temporary int, and then the reference ref binds to it as well, but that doesn't extend the lifetime of i, it still goes out of scope at the end of the constructor, leaving a dangling reference. You don't extend the underlying temporary's lifetime by binding another reference to it.
Your code might be safer if 1565 is approved and you make il a copy not a reference, but that issue is still open and doesn't even have proposed wording, let alone implementation experience.
Even if your example is meant to work, the wording regarding lifetime of the underlying array is obviously still being improved and it will take a while for compilers to implement whatever final semantics are settled on.
The wording you refer to in 8.5.4/6 is defective, and was corrected (somewhat) by DR1290. Instead of saying:
The lifetime of the array is the same as that of the initializer_list object.
... the amended standard now says:
The array has the same lifetime as any other temporary object (12.2 [class.temporary]), except that initializing an initializer_list object from the array extends the lifetime of the array exactly like binding a reference to a temporary.
Therefore the controlling wording for the lifetime of the temporary array is 12.2/5, which says:
The lifetime of a temporary bound to the returned value in a function return statement is not extended; the temporary is destroyed at the end of the full-expression in the return statement
Therefore the noisydt objects are destroyed before the function returns.
Until recently, Clang had a bug that caused it to fail to destroy the underlying array for an initializer_list object in some circumstances. I've fixed that for Clang 3.4; the output for your test case from Clang trunk is:
destroyed
destroyed
destroyed
received
destroyed
destroyed
destroyed
received
... which is correct, per DR1290.
I am trying to understand why the compiler is complaining here:
// cexpr_test.cpp
#include <initializer_list>
constexpr int test_cexpr(std::initializer_list<const char*> x)
{
return (int) (*x.begin())[0]; // ensuring the value isn't optimized out.
}
int main()
{
constexpr int r1 = test_cexpr({ "why does this work," });
constexpr std::initializer_list<const char*> broken { "but this doesn't?" };
constexpr int r2 = test_cexpr(broken);
return r1 + r2;
}
The message produced when compiled with
g++ -std=c++11 -Wall -Werror cexpr_test.cpp
is as follows:
cexpr_test.cpp: In function ‘int main()’:
cexpr_test.cpp:12:76: error: ‘const std::initializer_list{((const char* const*)(&)), 1}’ is not a constant expression
12 | constexpr std::initializer_list broken { "but this doesn't?" };
|
It's confusing why it constructs the first initializer list without any issues. What am I missing here?
The problem is with the initialization of broken itself here. What is a std::initializer_list and what does it hold? It's a reference type (i.e. refers somehow to another objects), and it's backed by a c-style array. The properties of this c-style array are what determines if the initializer_list can be a constexpr variable. We can consult [dcl.init.list] for those properties.
5 An object of type std::initializer_list<E> is constructed
from an initializer list as if the implementation generated and
materialized a prvalue of type “array of N const E”, where N is
the number of elements in the initializer list. Each element of that
array is copy-initialized with the corresponding element of the
initializer list, and the std::initializer_list<E> object is
constructed to refer to that array. [ Note: A constructor or
conversion function selected for the copy shall be accessible in the
context of the initializer list. — end note ] If a narrowing
conversion is required to initialize any of the elements, the program
is ill-formed. [ Example:
struct X {
X(std::initializer_list<double> v);
};
X x{ 1,2,3 };
The initialization will be implemented in a way roughly equivalent to
this:
const double __a[3] = {double{1}, double{2}, double{3}};
X x(std::initializer_list<double>(__a, __a+3));
assuming that the implementation can construct an initializer_list
object with a pair of pointers. — end example ]
6 The array has the same lifetime as any other temporary object,
except that initializing an initializer_list object from the array
extends the lifetime of the array exactly like binding a reference to
a temporary. [ Example:
typedef std::complex<double> cmplx;
std::vector<cmplx> v1 = { 1, 2, 3 };
void f() {
std::vector<cmplx> v2{ 1, 2, 3 };
std::initializer_list<int> i3 = { 1, 2, 3 };
}
struct A {
std::initializer_list<int> i4;
A() : i4{ 1, 2, 3 } {} // ill-formed, would create a dangling reference
};
For v1 and v2, the initializer_list object is a parameter in a
function call, so the array created for { 1, 2, 3 } has
full-expression lifetime. For i3, the initializer_list object is
a variable, so the array persists for the lifetime of the variable.
For i4, the initializer_list object is initialized in the
constructor's ctor-initializer as if by binding a temporary array to a
reference member, so the program is ill-formed ([class.base.init]).
— end example ] [ Note: The implementation is free to allocate the
array in read-only memory if an explicit array with the same
initializer could be so allocated. — end note ]
So this array is like any other temporary object that is referred to by a constant reference. Which means we can actually reduce your minimal example to something even smaller
constexpr int test_cexpr(int const & x)
{
return x;
}
int main()
{
constexpr int r1 = test_cexpr(0);
constexpr int const &broken = 0;
constexpr int r2 = test_cexpr(broken);
return r1 + r2;
}
This produces the exact same behavior and error. We can pass 0 directly as an argument to the constexpr function, the reference binds, and we can even refer to it inside the function. However, a constexpr reference may not be initialized with 0. The reason for zero not being a valid initializer, is that it requires materializing a temporary int object. This temporary is not a static variable, and so may not be used to initialize a constexpr reference. Simple as that.
The same reasoning applies to your case. The temporary array that's materialized is not an object with static storage duration, so it may not be used to initialize a constexpr reference type.
The reason it works when directly passing the argument to test_cexpr, is that the corresponding parameter is not itself a constexpr variable. Which means it can bind successfully. After which, the thing it's bound to just has to be usable in a constant expression. Without going into too much detail over this: since the temporary in that case has full-expression lifetime (and not lifetime extended), it is usable in a constant expression.
It's been a while since I last looked at temporary lifetime rules and I don't remember how member rvalue references affect
lifetime.
For example take the two following pieces of code:
int main()
{
std::get<0>(std::forward_as_tuple(
[](int v){ std::cout << v << std::endl; }
))(6);
return 0;
}
,
int main()
{
auto t = std::forward_as_tuple(
[](int v){ std::cout << v << std::endl; }
);
std::get<0>(t)(6);
return 0;
}
If member rvalue references don't affect lifetime rules, I would expect the first example to be well-behaved while the second example
to be undefined (since the full-expression containing the lambda object
ends at the first semicolon).
How do C++11, C++14 and C++17 treat the given examples? Are there differences between the three?
Lifetime extension applies when you directly bind a temporary to a reference anywhere but within a constructor initializer list. (Note: aggregate initialization isn't a constructor)
std::forward_as_tuple is a function. Any temporary passed to it cannot be lifetime extended beyond the current line.
Temporaries by default last until the end of the current line, basically. (What exactly that spot is isn't actually the end of the current line). In your two cases, it is the end of the current line (the ;) where the temporary ends its lifetime. This is long enough for the first case; in the second case, the temporary is dead and your code exhibits undefined behavior.
At the same time:
struct foo {
int&& x;
};
int main() {
foo f{3};
std::cout << f.x << "\n";
}
is perfectly well defined. No constructor, we bound a temporary to an (rvalue) reference, thus lifetime is extended.
Add this:
struct foo {
int&& x;
foo(int&& y):x(y) {}
};
or
struct foo {
int&& x;
foo(int y):x((int)y) {}
};
and it is now UB.
The first one because we bound the temporary to an rvalue reference when invoking the ctor. The inside of the constructor is irrelevant, as there is no temporary being bound directly there. Then the argument to the function and the temporary both go out of scope in main.
The second because the rule that binding the temporary (int)y0 to int&&x in a constructor initializer list does not extend lifetime the same way as it does elsewhere.
The rules of temporary lifetime extension haven't changed in any version of C++ since 98. We may have new ways to manifest temporaries, but once they exist, their lifetimes are well understood.
It should be noted that your example doesn't really apply to member references of any kind. You're calling a function with a temporary, and the parameter is a forwarding reference. The temporary in question is therefore bound to the function parameter reference, not to a member reference. The lifetime of that temporary will end after the expression that invoked that function, as it would with any temporary passed to a reference parameter.
The fact that this function (forward_as_tuple) will eventually store that reference in a tuple is irrelevant. What you do with a reference cannot alter its lifetime.
Again, that's C++98, and none of the later versions changed that.
Because this is a language-lawyer question. The rules for lifetime extension are in [class.temporary]. The wording from C++11 to C++14 to C++17 didn't change in a way that is relevant to this particular question. The rule is:
There are [two/three†] contexts in which temporaries are destroyed at a different point than the end of the full-expression. The first context is when a default constructor is called to initialize an element of an array [...]†
The [second/third†] 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.
This expression:
std::forward_as_tuple([](int v){ std::cout << v << std::endl; })
involves binding a reference (the parameter in forward_as_tuple) to a prvalue (the lambda-expression), which is explicitly mentioned in C++11/14 as a context which creates a temporary:
Temporaries of class type are created in various contexts: binding a reference to a prvalue, [...]
which in C++17 is worded as:
Temporary objects are created
(1.1) — when a prvalue is materialized so that it can be used as a glvalue (4.4),
Either way, we have a temporary, it's bound to a reference in a function call, so the temporary object persists until the completion of the full-epxression containing the call.
So this is okay:
std::get<0>(std::forward_as_tuple(
[](int v){ std::cout << v << std::endl; }
))(6);
But this calls through a dangling reference:
auto t = std::forward_as_tuple(
[](int v){ std::cout << v << std::endl; }
);
std::get<0>(t)(6);
because the lifetime of the temporary function object ended at the end of the statement initializing t.
Note that this has nothing to do with member rvalue references. If we had something like:
struct Wrapper {
X&& x;
};
Wrapper w{X()};
then the lifetime of the temporary X persists through the lifetime of w, and w.x isn't a dangling reference. But that's because there's no function call.
†C++17 introduced a 3rd context which involves copying an array, which is unrelated here.
I was reading these two paragraphs of the FDIS (12.2p{4,5}):
There are two contexts in which temporaries are destroyed at a different point than the end of the full-expression. The first context is when a default constructor is called to initialize an element of an array. If the constructor has one or more default arguments, the destruction of every temporary created in a default argument is sequenced before the construction of the next array element, if any.
and
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 bound to a reference parameter in a function call (5.2.2) persists until the completion of the full-expression containing the call.
These two two seem to contradict for the following case
struct A {
A() { std::cout << "C" << std::endl; }
~A() { std::cout << "D" << std::endl; }
};
struct B {
B(A const& a = A()) { }
};
typedef B array[2];
int main() {
array{};
}
Will this output CDCD as required by the first context, or will this output CCDD as required by the second context? GCC seems to follow the second context description and outputs CCDD. Have I overlooked something important?
EDIT: I don't think it needs C++0x. This new-expression is affected too by my question:
new array(); /* CDCD or CCDD ?? */
In this case though, GCC follows the first context, and outputs CDCD.
I don't think there's a contradiction.
5.2.2 clearly says what a function call is. A function call is a postfix expression followed by parentheses
containing a possibly empty,
comma-separated list of expressions
which constitute the arguments to the
function.
There doesn't seem to be a function call to B::B(A const&) anywhere in your program, so I don't see how the second passage applies.
EDIT the above is probably incorrect, given 1.9p10 etc.