[gcc r16-8748] libstdc++: Fix constantness of engaged -> disengaged std::optional [PR124910]
Patrick Palka
ppalka@gcc.gnu.org
Sat Apr 18 23:26:35 GMT 2026
https://gcc.gnu.org/g:f3e63a9fb3b40f81b794b369a1ab616f01e69730
commit r16-8748-gf3e63a9fb3b40f81b794b369a1ab616f01e69730
Author: Patrick Palka <ppalka@redhat.com>
Date: Sat Apr 18 19:25:59 2026 -0400
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>
Diff:
---
libstdc++-v3/include/std/optional | 6 ++
.../testsuite/20_util/optional/constexpr/124910.cc | 74 ++++++++++++++++++++++
2 files changed, 80 insertions(+)
diff --git a/libstdc++-v3/include/std/optional b/libstdc++-v3/include/std/optional
index 0f4cf0bd1ef6..49ba7b6b45dd 100644
--- a/libstdc++-v3/include/std/optional
+++ b/libstdc++-v3/include/std/optional
@@ -321,6 +321,12 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
{
_M_engaged = false;
_M_payload._M_value.~_Stored_type();
+#if __cpp_lib_optional >= 202106L // full constexpr support
+ if (std::is_constant_evaluated())
+ // Ensure union _M_payload always has an active member, for sake
+ // of the core constant expression requirements.
+ std::construct_at(std::__addressof(_M_payload._M_empty));
+#endif
}
#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..2f61f7e4e775
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/optional/constexpr/124910.cc
@@ -0,0 +1,74 @@
+// { 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;
+ }();
+
+ struct C : std::optional<T> {
+ constexpr C() : std::optional<T>(1) { this->reset(); }
+ };
+ constexpr C x6;
+}
+
+int
+main()
+{
+ do_test<int>();
+ do_test<A>();
+ do_test<B>();
+}
More information about the Libstdc++-cvs
mailing list