Skip to content

Commit f3e63a9

Browse files
author
Patrick Palka
committed
libstdc++: Fix constantness of engaged -> disengaged std::optional [PR124910]
When an optional that contains a value is cleared, _M_destroy invokes the destructor of the contained value _Storage::_M_value, leaving the union _Storage without an active member. While this is benign at runtime, a union suboject with no active member violates core constant expression requirements and in turn an optional in this state can't be used as a constant initializer, which Clang and recent GCC (since r16-3022) correctly diagnose. To fix this, this patch makes _M_destroy activate the dummy union member _M_empty after destroying _M_value to ensure that the union always has an active member throughout its lifetime. We use std::construct_at instead of simple assignment to work around a front end bug (comment #3 in the PR). Doing so means we don't activate the member in C++17 mode, which should be fine; I don't think it's possible to disengage an engaged optional using only the C++17 constexpr optional operations. PR c++/124910 libstdc++-v3/ChangeLog: * include/std/optional (_Optional_payload_base::_M_destroy) [__cpp_lib_optional >= 202106L]: During constant evaluation, after invoking destructor of _M_value, use construct_at to activate _M_empty. * testsuite/20_util/optional/constexpr/124910.cc: New test. Reviewed-by: Jonathan Wakely <jwakely@redhat.com>
1 parent fdb9155 commit f3e63a9

2 files changed

Lines changed: 80 additions & 0 deletions

File tree

libstdc++-v3/include/std/optional

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,12 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
321321
{
322322
_M_engaged = false;
323323
_M_payload._M_value.~_Stored_type();
324+
#if __cpp_lib_optional >= 202106L // full constexpr support
325+
if (std::is_constant_evaluated())
326+
// Ensure union _M_payload always has an active member, for sake
327+
// of the core constant expression requirements.
328+
std::construct_at(std::__addressof(_M_payload._M_empty));
329+
#endif
324330
}
325331

326332
#if __cplusplus >= 202002L
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// { dg-do compile { target c++20 } }
2+
3+
// PR124910 - bogus 'std::optional{...}' is not a constant expression error
4+
// after resetting it via '= nullopt'
5+
6+
#include <optional>
7+
8+
struct A
9+
{
10+
constexpr A(int m) : m(m) { }
11+
int m;
12+
};
13+
14+
struct B
15+
{
16+
constexpr B(int m) : m(m) { }
17+
constexpr ~B() { }
18+
int m;
19+
};
20+
21+
static_assert( std::is_trivially_destructible_v<int> );
22+
static_assert( std::is_trivially_destructible_v<A> );
23+
static_assert( ! std::is_trivially_destructible_v<B> );
24+
25+
template<class T>
26+
void
27+
do_test()
28+
{
29+
constexpr std::optional<T> x1 = [] {
30+
std::optional<T> o = 1;
31+
o = std::nullopt;
32+
return o;
33+
}();
34+
35+
constexpr std::optional<T> x2 = [] {
36+
std::optional<T> o = 1;
37+
o.reset();
38+
return o;
39+
}();
40+
41+
constexpr std::optional<T> x3 = [] {
42+
std::optional<T> o1 = 1;
43+
std::optional<long> o2;
44+
o1 = o2;
45+
return o1;
46+
}();
47+
48+
constexpr std::optional<T> x4 = [] {
49+
std::optional<T> o1 = 1;
50+
std::optional<long> o2;
51+
o1 = std::move(o2);
52+
return o1;
53+
}();
54+
55+
constexpr std::optional<T> x5 = [] {
56+
std::optional<T> o1 = 1;
57+
std::optional<T> o2;
58+
std::swap(o1, o2);
59+
return o1;
60+
}();
61+
62+
struct C : std::optional<T> {
63+
constexpr C() : std::optional<T>(1) { this->reset(); }
64+
};
65+
constexpr C x6;
66+
}
67+
68+
int
69+
main()
70+
{
71+
do_test<int>();
72+
do_test<A>();
73+
do_test<B>();
74+
}

0 commit comments

Comments
 (0)