https://gcc.gnu.org/g:b3c167b61fd75fe9820636d9f7fff7920ca9b084
commit r16-6301-gb3c167b61fd75fe9820636d9f7fff7920ca9b084 Author: Tomasz Kamiński <[email protected]> Date: Thu Dec 11 10:43:44 2025 +0100 libstdc++: Use union to store non-trivially destructible types in C++17 mode [PR112591] 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_USE_VARIANT_CXX17_OLD_ABI. However, presence of this macro has no effect in C++20 or later modes. PR libstdc++/112591 libstdc++-v3/ChangeLog: * include/std/variant (_Uninitialized::_M_get, __get_n) (_Uninitialized<_Type, false>): Add _GLIBCXX_USE_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. Reviewed-by: Jonathan Wakely <[email protected]> Signed-off-by: Tomasz Kamiński <[email protected]> Diff: --- libstdc++-v3/include/std/variant | 13 +++---- libstdc++-v3/testsuite/20_util/variant/112591.cc | 16 ++++++-- .../testsuite/20_util/variant/112591_compat.cc | 4 ++ .../testsuite/20_util/variant/constinit.cc | 40 ++++++++++++++++++++ .../testsuite/20_util/variant/constinit_compat.cc | 43 ++++++++++++++++++++++ 5 files changed, 106 insertions(+), 10 deletions(-) diff --git a/libstdc++-v3/include/std/variant b/libstdc++-v3/include/std/variant index 51d21daf46c3..12cbfd7cc268 100644 --- a/libstdc++-v3/include/std/variant +++ b/libstdc++-v3/include/std/variant @@ -211,7 +211,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_USE_VARIANT_CXX17_OLD_ABI) template<typename _Type, bool = std::is_trivially_destructible_v<_Type>> struct _Uninitialized; #else @@ -230,7 +230,7 @@ namespace __variant : _M_storage(std::forward<_Args>(__args)...) { } -#if __cpp_lib_variant < 202106L +#if (__cpp_lib_variant < 202106L) && defined(_GLIBCXX_USE_VARIANT_CXX17_OLD_ABI) constexpr const _Type& _M_get() const & noexcept { return _M_storage; } @@ -247,10 +247,9 @@ namespace __variant _Type _M_storage; }; -#if __cpp_lib_variant < 202106L +#if (__cpp_lib_variant < 202106L) && defined(_GLIBCXX_USE_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> { @@ -291,7 +290,7 @@ namespace __variant return __variant::__get_n<_Np - 3>( std::forward<_Union>(__u)._M_rest._M_rest._M_rest); } -#else +#else // !_GLIBCXX_USE_VARIANT_CXX17_OLD_ABI template<size_t _Np, typename _Union> constexpr auto&& __get_n(_Union&& __u) noexcept @@ -306,7 +305,7 @@ namespace __variant return __variant::__get_n<_Np - 3>( std::forward<_Union>(__u)._M_rest._M_rest._M_rest); } -#endif +#endif // !_GLIBCXX_USE_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 b1b07c4f46e2..2c5b8c9ce78f 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_USE_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 000000000000..411d1c15d54f --- /dev/null +++ b/libstdc++-v3/testsuite/20_util/variant/112591_compat.cc @@ -0,0 +1,4 @@ +// { dg-options "-D_GLIBCXX_USE_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 000000000000..48d647b1006a --- /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<1>, 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 000000000000..374861728ae8 --- /dev/null +++ b/libstdc++-v3/testsuite/20_util/variant/constinit_compat.cc @@ -0,0 +1,43 @@ +// { dg-options "-D_GLIBCXX_USE_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<1>, 2.0); +// { dg-final { scan-assembler-dem "(std::in_place_index_t<1ul?>, double&&)" } } +
