Date: Wed, 1 Apr 2026 10:32:01 +0300
As I read the standard:
- According to [intro.execution]/5
<https://eel.is/c++draft/intro.execution#5>, an *init-declarator* in a
*declaration-statement* is a *full-expression*. The same paragraph states
<https://eel.is/c++draft/intro.execution#5.sentence-4> that
initialization is part of the full-expression. Therefore, completion of the
initialization of a variable (and thus the completion of its creation)
requires completion of the corresponding full-expression.
- According to [stmt.dcl]/2 <https://eel.is/c++draft/stmt.dcl#2>, a
variable becomes *active* only after its init-declarator is complete
(i.e., after control proceeds to the next full-expression or statement).
Only then is it subject to destruction when control exits its lifetime.
- According to [dcl.init.general]/16.6.1
<https://eel.is/c++draft/dcl.init.general#16.6.1>, when an object is
initialized from a prvalue of the same type, the initializer expression is
used to initialize the *destination object*.
- According to [basic.lval]/5 <https://eel.is/c++draft/basic.lval#5>, a
prvalue generally has a *result object*. Combined with the previous
point, this implies that a prvalue can directly initialize the destination
object (i.e., the variable being initialized), without introducing
intermediate temporaries. This is also consistent with
[class.temporary]/2 <https://eel.is/c++draft/class.temporary#2>.
- According to [class.temporary]/4
<https://eel.is/c++draft/class.temporary#4>, temporary objects are
destroyed as the last step in evaluating the full-expression that
(lexically) contains their creation, even if that evaluation exits via an
exception.
Consider the following example:
struct B {
~B() noexcept (false) { throw 0; }
};
struct S {};
int main() {
S s = (B{}, S{});
}
In this case, the full-expression includes (in evaluation order):
1. construction of B
2. construction of S
3. initialization of s (directly from the prvalue S)
4. destruction of B
However, the destruction of B throws an exception. Therefore, the
full-expression does not complete normally, and control does not proceed to
the next full-expression or statement.
>From the above premises, it appears that:
- the initialization of s does not complete,
- s does not become active,
- and thus s is not subject to destruction.
At the same time, the result object of the prvalue S appears to have been
constructed directly in the storage of s (as the destination object), i.e.,
no separate temporary object exists.
This seems to leave the standard without a clear rule describing how such a
constructed result object is to be destroyed, since:
- it is not a temporary (and thus not covered by [class.temporary]/4),
- and it is not an active variable (and thus not subject to destruction
via [stmt.dcl]/2).
For comparison, in the case of an expression-statement:
int main() {
(B{}, S{});
}
both objects are temporaries, and their destruction is fully specified by
[class.temporary].
A similar question arises for reference initialization:
int main() {
S &&s = (B{}, S{});
}
By analogous reasoning:
- the lifetime of the reference s does not begin (since initialization
does not complete),
- therefore, there is no lifetime extension of a temporary bound to the
reference.
What, then, is the status of the S object in this case?
One possible model is that a temporary becomes “bound to the reference”
only once the lifetime of the reference begins, implying a transition from
“temporary” to “temporary with extended lifetime”. However, the standard
does not appear to describe such a transition explicitly.
I would appreciate clarification on whether the above reasoning is
incorrect, or whether this scenario is indeed underspecified.
- According to [intro.execution]/5
<https://eel.is/c++draft/intro.execution#5>, an *init-declarator* in a
*declaration-statement* is a *full-expression*. The same paragraph states
<https://eel.is/c++draft/intro.execution#5.sentence-4> that
initialization is part of the full-expression. Therefore, completion of the
initialization of a variable (and thus the completion of its creation)
requires completion of the corresponding full-expression.
- According to [stmt.dcl]/2 <https://eel.is/c++draft/stmt.dcl#2>, a
variable becomes *active* only after its init-declarator is complete
(i.e., after control proceeds to the next full-expression or statement).
Only then is it subject to destruction when control exits its lifetime.
- According to [dcl.init.general]/16.6.1
<https://eel.is/c++draft/dcl.init.general#16.6.1>, when an object is
initialized from a prvalue of the same type, the initializer expression is
used to initialize the *destination object*.
- According to [basic.lval]/5 <https://eel.is/c++draft/basic.lval#5>, a
prvalue generally has a *result object*. Combined with the previous
point, this implies that a prvalue can directly initialize the destination
object (i.e., the variable being initialized), without introducing
intermediate temporaries. This is also consistent with
[class.temporary]/2 <https://eel.is/c++draft/class.temporary#2>.
- According to [class.temporary]/4
<https://eel.is/c++draft/class.temporary#4>, temporary objects are
destroyed as the last step in evaluating the full-expression that
(lexically) contains their creation, even if that evaluation exits via an
exception.
Consider the following example:
struct B {
~B() noexcept (false) { throw 0; }
};
struct S {};
int main() {
S s = (B{}, S{});
}
In this case, the full-expression includes (in evaluation order):
1. construction of B
2. construction of S
3. initialization of s (directly from the prvalue S)
4. destruction of B
However, the destruction of B throws an exception. Therefore, the
full-expression does not complete normally, and control does not proceed to
the next full-expression or statement.
>From the above premises, it appears that:
- the initialization of s does not complete,
- s does not become active,
- and thus s is not subject to destruction.
At the same time, the result object of the prvalue S appears to have been
constructed directly in the storage of s (as the destination object), i.e.,
no separate temporary object exists.
This seems to leave the standard without a clear rule describing how such a
constructed result object is to be destroyed, since:
- it is not a temporary (and thus not covered by [class.temporary]/4),
- and it is not an active variable (and thus not subject to destruction
via [stmt.dcl]/2).
For comparison, in the case of an expression-statement:
int main() {
(B{}, S{});
}
both objects are temporaries, and their destruction is fully specified by
[class.temporary].
A similar question arises for reference initialization:
int main() {
S &&s = (B{}, S{});
}
By analogous reasoning:
- the lifetime of the reference s does not begin (since initialization
does not complete),
- therefore, there is no lifetime extension of a temporary bound to the
reference.
What, then, is the status of the S object in this case?
One possible model is that a temporary becomes “bound to the reference”
only once the lifetime of the reference begins, implying a transition from
“temporary” to “temporary with extended lifetime”. However, the standard
does not appear to describe such a transition explicitly.
I would appreciate clarification on whether the above reasoning is
incorrect, or whether this scenario is indeed underspecified.
Received on 2026-04-01 07:32:24
