[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