[PATCH] libstdc++: Fix constantness of engaged -> disengaged std::optional [PR124910]
Patrick Palka
ppalka@redhat.com
Sat Apr 18 01:44:05 GMT 2026
Tested on x86_64-pc-linux-gnu, does this look OK for trunk? Perhaps
backports as well?
-- >8 --
We implement std::optional<T> for trivially destructible T as a union
with an empty dummy member (_M_empty) indicating the disengaged state.
When we disengage such an std::optional that's already engaged we call
_M_destroy, which clears the _M_engaged flag and invokes the contained
objects's destructor, ending the lifetime of the corresponding union
member (_M_value) and leaving the union with no active member. While
benign at runtime, a union subobject with no active member violates
core constant expression requirements. Consequently the resulting
value can't be used as a constant initializer, which Clang and recent
GCC (r16-3022) correctly diagnose.
To fix this, this patch makes _M_destroy activate the _M_empty union
member after ending the destroying and deactivating _M_value. We use
std::construct_at instead of simple assignment to work around a front
end bug (see comment #6 of the PR).
PR c++/124910
libstdc++-v3/ChangeLog:
* include/std/optional (_Optional_payload_base::_M_destroy):
During constant evaluation, after invoking destructor of
_M_value, call construct_at to activate _M_empty.
* testsuite/20_util/optional/constexpr/124910.cc: New test.
---
libstdc++-v3/include/std/optional | 2 +
.../20_util/optional/constexpr/124910.cc | 63 +++++++++++++++++++
2 files changed, 65 insertions(+)
create mode 100644 libstdc++-v3/testsuite/20_util/optional/constexpr/124910.cc
diff --git a/libstdc++-v3/include/std/optional b/libstdc++-v3/include/std/optional
index 0f4cf0bd1ef6..0524222eab97 100644
--- a/libstdc++-v3/include/std/optional
+++ b/libstdc++-v3/include/std/optional
@@ -321,6 +321,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
{
_M_engaged = false;
_M_payload._M_value.~_Stored_type();
+ if (std::__is_constant_evaluated())
+ std::construct_at(std::__addressof(_M_payload._M_empty));
}
#if __cplusplus >= 202002L
diff --git a/libstdc++-v3/testsuite/20_util/optional/constexpr/124910.cc b/libstdc++-v3/testsuite/20_util/optional/constexpr/124910.cc
new file mode 100644
index 000000000000..f7facdec020d
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/optional/constexpr/124910.cc
@@ -0,0 +1,63 @@
+// { dg-do compile { target c++20 } }
+
+// PR124910 - bogus 'std::optional{...}' is not a constant expression error
+// after resetting it via '= nullopt'
+
+#include <optional>
+
+struct A {
+ constexpr A(int m) : m(m) { }
+ int m;
+};
+
+struct B {
+ constexpr B(int m) : m(m) { }
+ constexpr ~B() { }
+ int m;
+};
+
+static_assert( std::is_trivially_destructible_v<int> );
+static_assert( std::is_trivially_destructible_v<A> );
+static_assert( ! std::is_trivially_destructible_v<B> );
+
+template<class T>
+void do_test() {
+ constexpr std::optional<T> x1 = [] {
+ std::optional<T> o = 1;
+ o = std::nullopt;
+ return o;
+ }();
+
+ constexpr std::optional<T> x2 = [] {
+ std::optional<T> o = 1;
+ o.reset();
+ return o;
+ }();
+
+ constexpr std::optional<T> x3 = [] {
+ std::optional<T> o1 = 1;
+ std::optional<long> o2;
+ o1 = o2;
+ return o1;
+ }();
+
+ constexpr std::optional<T> x4 = [] {
+ std::optional<T> o1 = 1;
+ std::optional<long> o2;
+ o1 = std::move(o2);
+ return o1;
+ }();
+
+ constexpr std::optional<T> x5 = [] {
+ std::optional<T> o1 = 1;
+ std::optional<T> o2;
+ std::swap(o1, o2);
+ return o1;
+ }();
+}
+
+int main() {
+ do_test<int>();
+ do_test<A>();
+ do_test<B>();
+}
--
2.54.0.rc1.54.g60f07c4f5c
More information about the Libstdc++
mailing list