Created on 2021-09-06.00:00:00 last changed 16 months ago
Proposed resolution (approved by CWG 2022-11-11):
Change in 6.7.3 [basic.life] paragraph 5 as follows:
A program may end the lifetime of an object of class type without invoking the destructor, by reusing or releasing the storage as described above. [Note 3: A delete-expression (7.6.2.9 [expr.delete]) invokes the destructor prior to releasing the storage. —end note] In this case, the destructor is not implicitly invokedand any program that depends on the side effects produced by the destructor has undefined behavior. [Note: The correct behavior of a program often depends on the destructor being invoked for each object of class type. -- end note]
Additional notes (April, 2022):
The phrase "[a] program that depends on the side effects" may have these meanings:
The second option would need a fork in the evaluation of constant expressions to determine whether undefined behavior occurs.
[Accepted as a DR at the February, 2023 meeting.]
According to 7.7 [expr.const] bullet 5.8, one criterion that disqualifies an expression from being a core constant expression is:
an operation that would have undefined behavior as specified in Clause 4 [intro] through Clause 15 [cpp]
One potential source of undefined behavior is the omission of a call to a destructor for a constructed object, as described in 6.7.3 [basic.life] paragraph 5:
A program may end the lifetime of an object of class type without invoking the destructor, by reusing or releasing the storage as described above. [Note 3: A delete-expression (7.6.2.9 [expr.delete]) invokes the destructor prior to releasing the storage. —end note] In this case, the destructor is not implicitly invoked and any program that depends on the side effects produced by the destructor has undefined behavior.
For example:
#include <memory>
constexpr int test_basic_life_p5() {
class guard_t {
int &ref_;
public:
explicit constexpr guard_t(int &i) : ref_{i} {}
constexpr ~guard_t() { ref_ = 42; }
};
int result = 0;
auto alloc = std::allocator<guard_t>{};
auto pguard = alloc.allocate(1);
std::construct_at(pguard, result);
// std::destroy_at(pguard);
alloc.deallocate(pguard, 1);
return result; // value depends on destructor execution
}
int main() {
constexpr auto v = test_basic_life_p5();
return v;
}
It is not clear that it is reasonable to require implementations to diagnose this form of undefined behavior in constant expressions.
A somewhat related question is raised by the restrictions on the use of longjmp in 17.13.3 [csetjmp.syn] paragraph 2:
A setjmp/longjmp call pair has undefined behavior if replacing the setjmp and longjmp by catch and throw would invoke any non-trivial destructors for any objects with automatic storage duration.
Here the undefined behavior occurs for any non-trivial destructor that is skipped, not just one for which the program depends on its side effects, as in 6.7.3 [basic.life] paragraph 5. Perhaps these two specifications should be harmonized.
History | |||
---|---|---|---|
Date | User | Action | Args |
2023-07-16 13:00:43 | admin | set | status: open -> c++23 |
2023-07-16 13:00:43 | admin | set | status: dr -> open |
2023-02-18 18:43:04 | admin | set | status: ready -> dr |
2023-02-07 14:43:26 | admin | set | status: tentatively ready -> ready |
2022-11-20 07:54:16 | admin | set | status: open -> tentatively ready |
2022-04-19 05:57:55 | admin | set | messages: + msg6804 |
2022-04-19 05:57:55 | admin | set | messages: + msg6803 |
2021-09-06 00:00:00 | admin | create |