[RFC] libstdc++: Refactor _Variadic_union so _Unitialized<T, false> is not needed.
Tomasz Kaminski
tkaminsk@redhat.com
Mon Sep 1 08:08:18 GMT 2025
On Fri, Aug 29, 2025 at 5:23 PM Tomasz Kaminski <tkaminsk@redhat.com> wrote:
>
> On Fri, Aug 29, 2025 at 5:21 PM Tomasz Kaminski <tkaminsk@redhat.com>
> wrote:
>
>>
>>
>> On Fri, Aug 29, 2025 at 5:16 PM Patrick Palka <ppalka@redhat.com> wrote:
>>
>>> On Fri, 29 Aug 2025, Tomasz Kamiński wrote:
>>>
>>> > The changes the _Variadic_union implementation, in a way that the
>>> > _Unitialized<T, false> partial specialization for non-trivial types
>>> is not
>>> > necessary.
>>> >
>>> > This is simply done by separating the specialization for
>>> __trivially_destructible
>>> > being true and false, and for the later definining an empty destructor
>>> (similary
>>> > as it was done using concepts).
>>> >
>>> > We also reduce the number of specialization of _Varidic_union, so
>>> specialization
>>> > (int, int) is reused by (string, int, int) and (int,int). This is done
>>> by computing
>>> > initializating __trivially_destructible with conjuction of
>>> is_trivially_destructible_v
>>> > for remaining components. This is only necessary for non-trivial
>>> (false) specialization,
>>> > as if both _First and _Rest... are trivally destructible, then _Rest
>>> must also be.
>>> >
>>> > This also add test for PR112591.
>>> > ---
>>> > As in title, this is refernce for comments, espcially if we want to
>>> land this change,
>>> > or part of it to the trunk regardless of other changes. I think the
>>> test is valuable for sure,
>>> > but the separate specialization, and removal of _Variadic_union<false,
>>> int, int>
>>> > specialization seem usefull.
>>> >
>>> > Locally I have removed the _Unitialized<T, false> specialization, and
>>> the objects
>>> > no longer alias in C++17 mode (112591.cc works the same in C++20/17),
>>> and all
>>> > test have passed locally, so this seem correct. We could even remove
>>> the _Unitialized
>>> > altogether and just store _First direclty. This is however ABI break,
>>> as it does
>>> > affect layout of classes similar to one in the example.
>>> >
>>> >
>>> > libstdc++-v3/include/std/variant | 34 ++++++++++++++-----
>>> > .../testsuite/20_util/variant/112591.cc | 32 +++++++++++++++++
>>> > 2 files changed, 57 insertions(+), 9 deletions(-)
>>> > create mode 100644 libstdc++-v3/testsuite/20_util/variant/112591.cc
>>> >
>>> > diff --git a/libstdc++-v3/include/std/variant
>>> b/libstdc++-v3/include/std/variant
>>> > index 2f44f970028..f2f55837461 100644
>>> > --- a/libstdc++-v3/include/std/variant
>>> > +++ b/libstdc++-v3/include/std/variant
>>> > @@ -393,8 +393,29 @@ namespace __variant
>>> > _Variadic_union(in_place_index_t<_Np>, _Args&&...) = delete;
>>> > };
>>> >
>>> > - template<bool __trivially_destructible, typename _First,
>>> typename... _Rest>
>>> > - union _Variadic_union<__trivially_destructible, _First, _Rest...>
>>> > + template<typename _First, typename... _Rest>
>>> > + union _Variadic_union<true, _First, _Rest...>
>>> > + {
>>> > + constexpr _Variadic_union() : _M_rest() { }
>>> > +
>>> > + template<typename... _Args>
>>> > + constexpr
>>> > + _Variadic_union(in_place_index_t<0>, _Args&&... __args)
>>> > + : _M_first(in_place_index<0>, std::forward<_Args>(__args)...)
>>> > + { }
>>> > +
>>> > + template<size_t _Np, typename... _Args>
>>> > + constexpr
>>> > + _Variadic_union(in_place_index_t<_Np>, _Args&&... __args)
>>> > + : _M_rest(in_place_index<_Np-1>, std::forward<_Args>(__args)...)
>>> > + { }
>>> > +
>>> > + _Uninitialized<_First> _M_first;
>>> > + _Variadic_union<true, _Rest...> _M_rest;
>>> > + };
>>> > +
>>> > + template<typename _First, typename... _Rest>
>>> > + union _Variadic_union<false, _First, _Rest...>
>>> > {
>>> > constexpr _Variadic_union() : _M_rest() { }
>>> >
>>> > @@ -410,24 +431,19 @@ namespace __variant
>>> > : _M_rest(in_place_index<_Np-1>, std::forward<_Args>(__args)...)
>>> > { }
>>> >
>>> > -#if __cpp_lib_variant >= 202106L
>>> > _Variadic_union(const _Variadic_union&) = default;
>>> > _Variadic_union(_Variadic_union&&) = default;
>>> > _Variadic_union& operator=(const _Variadic_union&) = default;
>>> > _Variadic_union& operator=(_Variadic_union&&) = default;
>>> >
>>> > - ~_Variadic_union() = default;
>>> > -
>>> > // If any alternative type is not trivially destructible then
>>> we need a
>>> > // user-provided destructor that does nothing. The active
>>> alternative
>>> > // will be destroyed by _Variant_storage::_M_reset() instead of
>>> here.
>>> > - constexpr ~_Variadic_union()
>>> > - requires (!__trivially_destructible)
>>> > + _GLIBCXX20_CONSTEXPR ~_Variadic_union()
>>> > { }
>>> > -#endif
>>> >
>>> > _Uninitialized<_First> _M_first;
>>> > - _Variadic_union<__trivially_destructible, _Rest...> _M_rest;
>>> > + _Variadic_union<(is_trivially_destructible_v<_Rest> && ...),
>>> _Rest...> _M_rest;
>>>
>>> I believe this makes _Variadic_union instantiation quadratic with
>>> respect to the number of alternatives. How compile-time/memory
>>> usage fare for the 87619.cc test with this change? (can use
>>> -ftime-report to measure that)
>>>
>> I have adjusted the test to have a non-trivial destructor, so we hit this
>> specialization.
>> Results after:
>> Time variable wall GGC
>> phase setup : 0.00 ( 0%) 2015k ( 1%)
>> phase parsing : 0.06 ( 1%) 14M ( 6%)
>> phase lang. deferred : 0.56 ( 12%) 152M ( 65%)
>> phase opt and generate : 4.20 ( 87%) 66M ( 28%)
>> |name lookup : 0.01 ( 0%) 1884k ( 1%)
>> |overload resolution : 0.10 ( 2%) 25M ( 11%)
>> garbage collection : 0.11 ( 2%) 0 ( 0%)
>> callgraph construction : 0.12 ( 2%) 1377k ( 1%)
>> callgraph ipa passes : 0.04 ( 1%) 51k ( 0%)
>> preprocessing : 0.01 ( 0%) 1044k ( 0%)
>> parser (global) : 0.02 ( 0%) 10M ( 4%)
>> parser struct body : 0.02 ( 0%) 4272k ( 2%)
>> parser inl. meth. body : 0.01 ( 0%) 1392k ( 1%)
>> template instantiation : 0.40 ( 8%) 134M ( 57%)
>> constant expression evaluation : 0.05 ( 1%) 41k ( 0%)
>> symout : 4.08 ( 85%) 80M ( 34%)
>> TOTAL : 4.82 235M
>>
>> Results if I use hardcoded false instead of above && condition:
>> Time variable wall GGC
>> phase setup : 0.00 ( 0%) 2015k ( 1%)
>> phase parsing : 0.06 ( 1%) 14M ( 7%)
>> phase lang. deferred : 0.42 ( 9%) 116M ( 58%)
>> phase opt and generate : 4.21 ( 90%) 66M ( 33%)
>> |name lookup : 0.01 ( 0%) 1884k ( 1%)
>> |overload resolution : 0.02 ( 0%) 3277k ( 2%)
>> garbage collection : 0.10 ( 2%) 0 ( 0%)
>> callgraph construction : 0.12 ( 2%) 1377k ( 1%)
>> callgraph ipa passes : 0.03 ( 1%) 51k ( 0%)
>> preprocessing : 0.01 ( 0%) 1044k ( 1%)
>> parser (global) : 0.02 ( 0%) 10M ( 5%)
>> parser struct body : 0.01 ( 0%) 4272k ( 2%)
>> parser inl. meth. body : 0.01 ( 0%) 1392k ( 1%)
>> template instantiation : 0.30 ( 6%) 98M ( 49%)
>> constant expression evaluation : 0.01 ( 0%) 41k ( 0%)
>> symout : 4.10 ( 87%) 80M ( 40%)
>> TOTAL : 4.69 199M
>>
>> Template depth in 267 in both cases.
>>
> Does the above look acceptable to you? In less syntetic examples they may
be some
benefits from reducing the number of template instantiations.
>
>>
>>
>>>
>>> > };
>>> >
>>> > // _Never_valueless_alt is true for variant alternatives that can
>>> > diff --git a/libstdc++-v3/testsuite/20_util/variant/112591.cc
>>> b/libstdc++-v3/testsuite/20_util/variant/112591.cc
>>> > new file mode 100644
>>> > index 00000000000..b1b07c4f46e
>>> > --- /dev/null
>>> > +++ b/libstdc++-v3/testsuite/20_util/variant/112591.cc
>>> > @@ -0,0 +1,32 @@
>>> > +// { dg-do run { target c++17 } }
>>> > +
>>> > +#include <variant>
>>> > +#include <testsuite_hooks.h>
>>> > +
>>> > +struct NonEmpty { int x; };
>>> > +struct TrivialEmpty {};
>>> > +struct NonTrivialEmpty { ~NonTrivialEmpty() {} };
>>> > +
>>> > +template<typename T>
>>> > +struct Compose : T
>>> > +{
>>> > + std::variant<T, int> v;
>>> > +};
>>> > +
>>> > +template<typename T>
>>> > +bool testAlias()
>>> > +{
>>> > + Compose<T> c;
>>> > + return static_cast<T*>(&c) == &std::get<T>(c.v);
>>> > +}
>>> > +
>>> > +int main()
>>> > +{
>>> > + VERIFY( !testAlias<NonEmpty>() );
>>> > + VERIFY( !testAlias<TrivialEmpty>() );
>>> > +#if __cplusplus >= 202002L
>>> > + VERIFY( !testAlias<NonTrivialEmpty>() );
>>> > +#else
>>> > + VERIFY( testAlias<NonTrivialEmpty>() );
>>> > +#endif
>>> > +}
>>> > --
>>> > 2.50.1
>>> >
>>> >
>>
>>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://gcc.gnu.org/pipermail/libstdc++/attachments/20250901/5ad5c6e8/attachment-0001.htm>
More information about the Libstdc++
mailing list