On Thu, 11 Dec 2025 at 13:23, Tomasz Kamiński <[email protected]> wrote:
>
> This patch disables use of specialization _Uninitialized<_Type, false> for
> non-trivially destructible types by default in C++17, and fallbacks to
> the  primary template, that stores the type in union directly. This makes the
> ABI consistent between C++17 and C++20 (or later). This partial specialization
> is no longer required after the changes introduced in 
> r16-5961-g09bece00d0ec98.
>
> This fixes non-conformance in C++17 mode where global variables of a variant
> specialization type, were not statically-initialized for non-trivially
> destructible types, even if initialization of the selected alternative could
> be performed at compile time. For illustration, the following global variable
> will be statically initialized after this change:
>   std::variant<std::unique_ptr<T>, std::unique_ptr<U>> ptr;
>
> This constitutes an ABI break, and changes the layout of the types, that uses
> the same non-trivially copyable both as the base class, as alternative of the
> variant object that is first member:
>   struct EmptyNonTrivial { ~EmptyNonTrivial(); };
>   struct Affected : EmptyNonTrivial {
>     std::variant<EmptyNonTrivial, char> mem; // mem was at offset zero,
>                                              // will use non-zero offset now
>   };
> After changes the layout of such types consistent with one used for empty 
> types
> with trivial destructor, or one used for any empty type in C++20 or later.
>
> For programs affected by this change, it can be reverted in C++17 mode, by
>  defining _GLIBCXX_VARIANT_CXX17_OLD_ABI. However, presence of this macro
> has no effect in C++20 or later modes.

I think we want USE_VARIANT_CXX17_OLD_ABI, because we have "USE" in
_GLIBCXX_USE_CXX11_ABI (for string and list) and
_GLIBCXX_USE_ALLOC_PTR_FOR_xxx and now
_GLIBCXX_USE_OLD_GENERATE_CANONICAL.

OK for trunk with "USE" added to the macro name (and any line breaks
that might be needed in the #if lines)


>
>         PR libstdc++/112591
>
> libstdc++-v3/ChangeLog:
>
>         * include/std/variant (_Uninitialized::_M_get, __get_n)
>         (_Uninitialized<_Type, false>): Add _GLIBCXX_VARIANT_CXX17_OLD_ABI
>         check to preprocessor guard.
>         * testsuite/20_util/variant/112591.cc: Updated tests.
>         * testsuite/20_util/variant/112591_compat.cc: New test.
>         * testsuite/20_util/variant/constinit.cc: New test.
>         * testsuite/20_util/variant/constinit_compat.cc: New test.
> ---
>  libstdc++-v3/include/std/variant              | 13 +++---
>  .../testsuite/20_util/variant/112591.cc       | 16 +++++--
>  .../20_util/variant/112591_compat.cc          |  4 ++
>  .../testsuite/20_util/variant/constinit.cc    | 40 +++++++++++++++++
>  .../20_util/variant/constinit_compat.cc       | 43 +++++++++++++++++++
>  5 files changed, 106 insertions(+), 10 deletions(-)
>  create mode 100644 libstdc++-v3/testsuite/20_util/variant/112591_compat.cc
>  create mode 100644 libstdc++-v3/testsuite/20_util/variant/constinit.cc
>  create mode 100644 libstdc++-v3/testsuite/20_util/variant/constinit_compat.cc
>
> diff --git a/libstdc++-v3/include/std/variant 
> b/libstdc++-v3/include/std/variant
> index f2f55837461..743460ab043 100644
> --- a/libstdc++-v3/include/std/variant
> +++ b/libstdc++-v3/include/std/variant
> @@ -210,7 +210,7 @@ namespace __variant
>      __as(const std::variant<_Types...>&& __v) noexcept
>      { return std::move(__v); }
>
> -#if __cpp_lib_variant < 202106L
> +#if (__cpp_lib_variant < 202106L) && defined(_GLIBCXX_VARIANT_CXX17_OLD_ABI)
>    template<typename _Type, bool = std::is_trivially_destructible_v<_Type>>
>      struct _Uninitialized;
>  #else
> @@ -229,7 +229,7 @@ namespace __variant
>         : _M_storage(std::forward<_Args>(__args)...)
>         { }
>
> -#if __cpp_lib_variant < 202106L
> +#if (__cpp_lib_variant < 202106L) && defined(_GLIBCXX_VARIANT_CXX17_OLD_ABI)
>        constexpr const _Type& _M_get() const & noexcept
>        { return _M_storage; }
>
> @@ -246,10 +246,9 @@ namespace __variant
>        _Type _M_storage;
>      };
>
> -#if __cpp_lib_variant < 202106L
> +#if (__cpp_lib_variant < 202106L) && defined(_GLIBCXX_VARIANT_CXX17_OLD_ABI)
>    // This partial specialization is used for non-trivially destructible types
> -  // in C++17, so that _Uninitialized<T> is trivially destructible and can be
> -  // used as a union member in _Variadic_union.
> +  // in C++17.
>    template<typename _Type>
>      struct _Uninitialized<_Type, false>
>      {
> @@ -290,7 +289,7 @@ namespace __variant
>         return __variant::__get_n<_Np - 3>(
>                  std::forward<_Union>(__u)._M_rest._M_rest._M_rest);
>      }
> -#else
> +#else // !_GLIBCXX_VARIANT_CXX17_OLD_ABI
>    template<size_t _Np, typename _Union>
>      constexpr auto&&
>      __get_n(_Union&& __u) noexcept
> @@ -305,7 +304,7 @@ namespace __variant
>         return __variant::__get_n<_Np - 3>(
>                  std::forward<_Union>(__u)._M_rest._M_rest._M_rest);
>      }
> -#endif
> +#endif // !_GLIBCXX_VARIANT_CXX17_OLD_ABI
>
>    // Returns the typed storage for __v.
>    template<size_t _Np, typename _Variant>
> diff --git a/libstdc++-v3/testsuite/20_util/variant/112591.cc 
> b/libstdc++-v3/testsuite/20_util/variant/112591.cc
> index b1b07c4f46e..8852eb59faa 100644
> --- a/libstdc++-v3/testsuite/20_util/variant/112591.cc
> +++ b/libstdc++-v3/testsuite/20_util/variant/112591.cc
> @@ -4,6 +4,15 @@
>  #include <testsuite_hooks.h>
>
>  struct NonEmpty { int x; };
> +struct NonTrivial
> +{
> +  constexpr NonTrivial() : x(0) {}
> +  constexpr NonTrivial(int p) : x(p) {}
> +  ~NonTrivial() {}
> +
> +  int x;
> +};
> +
>  struct TrivialEmpty {};
>  struct NonTrivialEmpty { ~NonTrivialEmpty() {} };
>
> @@ -23,10 +32,11 @@ bool testAlias()
>  int main()
>  {
>    VERIFY( !testAlias<NonEmpty>() );
> +  VERIFY( !testAlias<NonTrivial>() );
>    VERIFY( !testAlias<TrivialEmpty>() );
> -#if __cplusplus >= 202002L
> +#if (__cplusplus >= 202002L) || !defined(_GLIBCXX_VARIANT_CXX17_OLD_ABI)
>    VERIFY( !testAlias<NonTrivialEmpty>() );
> -#else
> -  VERIFY( testAlias<NonTrivialEmpty>() );
> +#else
> +  VERIFY(  testAlias<NonTrivialEmpty>() );
>  #endif
>  }
> diff --git a/libstdc++-v3/testsuite/20_util/variant/112591_compat.cc 
> b/libstdc++-v3/testsuite/20_util/variant/112591_compat.cc
> new file mode 100644
> index 00000000000..0bc748fcdf6
> --- /dev/null
> +++ b/libstdc++-v3/testsuite/20_util/variant/112591_compat.cc
> @@ -0,0 +1,4 @@
> +// { dg-options "-D_GLIBCXX_VARIANT_CXX17_OLD_ABI" }
> +// { dg-do run { target c++17 } }
> +
> +#include "112591.cc"
> diff --git a/libstdc++-v3/testsuite/20_util/variant/constinit.cc 
> b/libstdc++-v3/testsuite/20_util/variant/constinit.cc
> new file mode 100644
> index 00000000000..df6ec91841c
> --- /dev/null
> +++ b/libstdc++-v3/testsuite/20_util/variant/constinit.cc
> @@ -0,0 +1,40 @@
> +// { dg-do compile { target c++17 } }
> +
> +#include <variant>
> +
> +struct NonEmpty { int x; };
> +struct NonTrivial
> +{
> +  constexpr NonTrivial() : x(0) {}
> +  NonTrivial(int p) : x(p) {}
> +  ~NonTrivial() {}
> +
> +  int x;
> +};
> +
> +struct TrivialEmpty {};
> +struct NonTrivialEmpty
> +{
> +  NonTrivialEmpty() = default;
> +  NonTrivialEmpty(float) {}
> +  ~NonTrivialEmpty() {}
> +};
> +
> +std::variant<NonEmpty> vNonEmpty(std::in_place_type<NonEmpty>);
> +// { dg-final { scan-assembler-dem-not "(std::in_place_type_t<NonEmpty>)" } }
> +
> +std::variant<NonTrivial> vNonTrivial(std::in_place_type<NonTrivial>);
> +// { dg-final { scan-assembler-dem-not "(std::in_place_type_t<NonTrivial>)" 
> } }
> +
> +std::variant<int, NonTrivial> 
> vNonTrivialNonConstexpr(std::in_place_index<1>, 2);
> +// { dg-final { scan-assembler-dem "(std::in_place_index_t<1ul>, int&&)" } }
> +
> +std::variant<TrivialEmpty> vTrivialEmpty(std::in_place_type<TrivialEmpty>);
> +// { dg-final { scan-assembler-dem-not 
> "(std::in_place_type_t<TrivialEmpty>)" } }
> +
> +std::variant<NonTrivialEmpty> 
> vNonTrivialEmpty(std::in_place_type<NonTrivialEmpty>);
> +// { dg-final { scan-assembler-dem-not 
> "(std::in_place_type_t<NonTrivialEmpty>)" } }
> +
> +std::variant<int, NonTrivialEmpty> 
> vNonTrivialEmptyNonConstexpr(std::in_place_index<1ul>, 2.0);
> +// { dg-final { scan-assembler-dem "(std::in_place_index_t<1ul>, double&&)" 
> } }
> +
> diff --git a/libstdc++-v3/testsuite/20_util/variant/constinit_compat.cc 
> b/libstdc++-v3/testsuite/20_util/variant/constinit_compat.cc
> new file mode 100644
> index 00000000000..842cd1a6d16
> --- /dev/null
> +++ b/libstdc++-v3/testsuite/20_util/variant/constinit_compat.cc
> @@ -0,0 +1,43 @@
> +// { dg-options "-D_GLIBCXX_VARIANT_CXX17_OLD_ABI" }
> +// { dg-do compile { target c++17 } }
> +
> +#include <variant>
> +
> +struct NonEmpty { int x; };
> +struct NonTrivial
> +{
> +  constexpr NonTrivial() : x(0) {}
> +  NonTrivial(int p) : x(p) {}
> +  ~NonTrivial() {}
> +
> +  int x;
> +};
> +
> +struct TrivialEmpty {};
> +struct NonTrivialEmpty
> +{
> +  NonTrivialEmpty() = default;
> +  NonTrivialEmpty(float) {}
> +  ~NonTrivialEmpty() {}
> +};
> +
> +std::variant<NonEmpty> vNonEmpty(std::in_place_type<NonEmpty>);
> +// { dg-final { scan-assembler-dem-not "(std::in_place_type_t<NonEmpty>)" } }
> +
> +std::variant<NonTrivial> vNonTrivial(std::in_place_type<NonTrivial>);
> +// { dg-final { scan-assembler-dem "(std::in_place_type_t<NonTrivial>)" { 
> target { ! c++20 } } } }
> +// { dg-final { scan-assembler-dem-not "(std::in_place_type_t<NonTrivial>)" 
> { target c++20 } } }
> +
> +std::variant<int, NonTrivial> 
> vNonTrivialNonConstexpr(std::in_place_index<1>, 2);
> +// { dg-final { scan-assembler-dem "(std::in_place_index_t<1ul>, int&&)" } }
> +
> +std::variant<TrivialEmpty> vTrivialEmpty(std::in_place_type<TrivialEmpty>);
> +// { dg-final { scan-assembler-dem-not 
> "(std::in_place_type_t<TrivialEmpty>)" } }
> +
> +std::variant<NonTrivialEmpty> 
> vNonTrivialEmpty(std::in_place_type<NonTrivialEmpty>);
> +// { dg-final { scan-assembler-dem "(std::in_place_type_t<NonTrivialEmpty>)" 
> { target { ! c++20 } } } }
> +// { dg-final { scan-assembler-dem-not 
> "(std::in_place_type_t<NonTrivialEmpty>)" { target c++20 } } }
> +
> +std::variant<int, NonTrivialEmpty> 
> vNonTrivialEmptyNonConstexpr(std::in_place_index<1ul>, 2.0);
> +// { dg-final { scan-assembler-dem "(std::in_place_index_t<1ul>, double&&)" 
> } }
> +
> --
> 2.52.0
>

Reply via email to