[committed v2] libstdc++: Fix constantness of engaged -> disengaged std::optional [PR124910]
Tomasz Kaminski
tkaminsk@redhat.com
Mon Apr 20 13:39:45 GMT 2026
On Mon, Apr 20, 2026 at 2:44 PM Patrick Palka <ppalka@redhat.com> wrote:
> On Mon, 20 Apr 2026, Tomasz Kaminski wrote:
>
> >
> >
> > On Sun, Apr 19, 2026 at 1:28 AM Patrick Palka <ppalka@redhat.com> wrote:
> > Changes in v2:
> > - Added code comment
> > - Clarified commit message, removed red herring about trivial
> > destructibility.
> > - Extended test with 'x6' direct-initialization that avoids
> invoking
> > optional's move constructor. This testcase, when instantiated
> with T=B,
> > demonstrates that non-trivially-copyable optional is also
> affected
> > and we must set _M_empty even in that case.
> >
> > -- >8 --
> >
> > 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.
> >
> > I am not aware of this limitation, and I do not see how if fails out of
> the wording,
> > The constituent values explicitly exclude inactive union members:
> >
> https://eel.is/c++draft/intro.objehttps://docs.google.com/https://docs.google.com/ct#12.2
> <https://eel.is/c++draft/intro.object#12.2>, so empty union simply has
> none.
>
> That term "constituent values" seems to have been introduced in C++26 by
> P2668R5 and so it's not clear to what extent that wording applies to C++20
> :/
>
For C++20 we have:
https://timsong-cpp.github.io/cppwp/n4861/dcl.constexpr#10
And there seem be no limitation of union without active members here:
https://timsong-cpp.github.io/cppwp/n4861/expr.const#5
>
> >
> > Could you please update the commit description, to reflect actual problem
> > as described by Jason here:
> > https://gcc.gnu.org/bugzilla/show_bug.cgi?id=124910#c13
>
> [class.union.general]/2 says: In a union, a non-static data member is
> active if its name refers to an object whose lifetime has begun and has
> not ended. How does destroying an active member not make it inactive?
>
I think the point of comment, is that this change is a workaround for GCC
bug
(the _M_value member is still considered active member, but it shouldn't).
>
>
> >
> >
> >
> > 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>
> > ---
> > libstdc++-v3/include/std/optional | 6 ++
> > .../20_util/optional/constexpr/124910.cc | 74
> +++++++++++++++++++
> > 2 files changed, 80 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..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>();
> > +}
> > --
> > 2.54.0.rc1.54.g60f07c4f5c
> >
> >
> >
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://gcc.gnu.org/pipermail/libstdc++/attachments/20260420/ce503408/attachment-0001.htm>
More information about the Libstdc++
mailing list