[1/3] libstdc++: Replace uses of EBO with [[no_unique_address]]
Commit Message
Clang 9 added support for [[__no_unique_address__]] and we don't support
Intel icc any longer, so we can remove the code in <tuple> that works
around the absence of that attribute. We can also address a FIXME in
<bits/shared_ptr_base.h> and replace uses of EBO with the attribute.
libstdc++-v3/ChangeLog:
* include/bits/shared_ptr_base.h (_Sp_ebo_helper): Simplify by
using [[__no_unique_address__]] instead of EBO. Use the
attribute unconditionally for the unstable ABI.
(_Sp_counted_deleter::_Impl): Adjust uses of _Sp_ebo_helper.
(_Sp_counted_ptr_inplace::_Impl): Likewise.
* include/std/tuple (_Head_base): Remove implementation for
compilers that don't support [[__no_unique_address__]]. Use the
attribute unconditionally for the unstable ABI.
---
Tested x86_64-linux.
libstdc++-v3/include/bits/shared_ptr_base.h | 65 +++++++++----------
libstdc++-v3/include/std/tuple | 69 ++++-----------------
2 files changed, 43 insertions(+), 91 deletions(-)
Comments
On Tue, 5 May 2026 at 16:07, Jonathan Wakely <jwakely@redhat.com> wrote:
>
> Clang 9 added support for [[__no_unique_address__]] and we don't support
> Intel icc any longer, so we can remove the code in <tuple> that works
> around the absence of that attribute. We can also address a FIXME in
> <bits/shared_ptr_base.h> and replace uses of EBO with the attribute.
>
> libstdc++-v3/ChangeLog:
>
> * include/bits/shared_ptr_base.h (_Sp_ebo_helper): Simplify by
> using [[__no_unique_address__]] instead of EBO. Use the
> attribute unconditionally for the unstable ABI.
> (_Sp_counted_deleter::_Impl): Adjust uses of _Sp_ebo_helper.
> (_Sp_counted_ptr_inplace::_Impl): Likewise.
> * include/std/tuple (_Head_base): Remove implementation for
> compilers that don't support [[__no_unique_address__]]. Use the
> attribute unconditionally for the unstable ABI.
> ---
>
> Tested x86_64-linux.
>
> libstdc++-v3/include/bits/shared_ptr_base.h | 65 +++++++++----------
> libstdc++-v3/include/std/tuple | 69 ++++-----------------
> 2 files changed, 43 insertions(+), 91 deletions(-)
>
> diff --git a/libstdc++-v3/include/bits/shared_ptr_base.h b/libstdc++-v3/include/bits/shared_ptr_base.h
> index b92e3a4c90e4..3a966eddc06c 100644
> --- a/libstdc++-v3/include/bits/shared_ptr_base.h
> +++ b/libstdc++-v3/include/bits/shared_ptr_base.h
> @@ -513,57 +513,52 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> inline void
> _Sp_counted_ptr<nullptr_t, _S_atomic>::_M_dispose() noexcept { }
>
> - // FIXME: once __has_cpp_attribute(__no_unique_address__)) is true for
> - // all supported compilers we can greatly simplify _Sp_ebo_helper.
> +#if ! __has_cpp_attribute(__no_unique_address__)
> +#error "support for [[__no_unique_address__]] attribute is required"
> +#endif
> +
> +#if ! _GLIBCXX_INLINE_VERSION
> // N.B. unconditionally applying the attribute could change layout for
> // final types, which currently cannot use EBO so have a unique address.
> -
> - template<int _Nm, typename _Tp,
> - bool __use_ebo = !__is_final(_Tp) && __is_empty(_Tp)>
> + template<typename _Tp, bool = !__is_final(_Tp) && __is_empty(_Tp)>
> struct _Sp_ebo_helper;
> +#else
> + template<typename _Tp, bool = true>
> + struct _Sp_ebo_helper;
> +#endif
>
> - /// Specialization using EBO.
> - template<int _Nm, typename _Tp>
> - struct _Sp_ebo_helper<_Nm, _Tp, true> : private _Tp
> + /// Specialization using [[no_unique_address]].
> + template<typename _Tp>
> + struct _Sp_ebo_helper<_Tp, true>
> {
> - explicit _Sp_ebo_helper(const _Tp& __tp) : _Tp(__tp) { }
> - explicit _Sp_ebo_helper(_Tp&& __tp) : _Tp(std::move(__tp)) { }
> -
> - static _Tp&
> - _S_get(_Sp_ebo_helper& __eboh) { return static_cast<_Tp&>(__eboh); }
> + [[__no_unique_address__]] _Tp _M_obj;
> };
>
> - /// Specialization not using EBO.
> - template<int _Nm, typename _Tp>
> - struct _Sp_ebo_helper<_Nm, _Tp, false>
> +#if ! _GLIBCXX_INLINE_VERSION
> + /// Specialization not using [[no_unique_address]].
> + template<typename _Tp>
> + struct _Sp_ebo_helper<_Tp, false>
> {
> - explicit _Sp_ebo_helper(const _Tp& __tp) : _M_tp(__tp) { }
> - explicit _Sp_ebo_helper(_Tp&& __tp) : _M_tp(std::move(__tp)) { }
> -
> - static _Tp&
> - _S_get(_Sp_ebo_helper& __eboh)
> - { return __eboh._M_tp; }
> -
> - private:
> - _Tp _M_tp;
> + _Tp _M_obj;
> };
> +#endif
>
> // Support for custom deleter and/or allocator
> template<typename _Ptr, typename _Deleter, typename _Alloc, _Lock_policy _Lp>
> class _Sp_counted_deleter final : public _Sp_counted_base<_Lp>
> {
> - class _Impl : _Sp_ebo_helper<0, _Deleter>, _Sp_ebo_helper<1, _Alloc>
> + class _Impl
> {
> - typedef _Sp_ebo_helper<0, _Deleter> _Del_base;
> - typedef _Sp_ebo_helper<1, _Alloc> _Alloc_base;
> + _Sp_ebo_helper<_Deleter> _M_d;
> + _Sp_ebo_helper<_Alloc> _M_a;
An off-list question from Nathan made me realize that we also need the
attribute on these members, not just inside the helper struct. I need
to check that this is actually working as intended.
>
> public:
> _Impl(_Ptr __p, _Deleter __d, const _Alloc& __a) noexcept
> - : _Del_base(std::move(__d)), _Alloc_base(__a), _M_ptr(__p)
> + : _M_d{std::move(__d)}, _M_a{__a}, _M_ptr(__p)
> { }
>
> - _Deleter& _M_del() noexcept { return _Del_base::_S_get(*this); }
> - _Alloc& _M_alloc() noexcept { return _Alloc_base::_S_get(*this); }
> + _Deleter& _M_del() noexcept { return _M_d._M_obj; }
> + _Alloc& _M_alloc() noexcept { return _M_a._M_obj; }
>
> _Ptr _M_ptr;
> };
> @@ -645,14 +640,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> template<typename _Tp, typename _Alloc, _Lock_policy _Lp>
> class _Sp_counted_ptr_inplace final : public _Sp_counted_base<_Lp>
> {
> - class _Impl : _Sp_ebo_helper<0, _Alloc>
> + class _Impl
> {
> - typedef _Sp_ebo_helper<0, _Alloc> _A_base;
> + _Sp_ebo_helper<_Alloc> _M_a;
>
> public:
> - explicit _Impl(_Alloc __a) noexcept : _A_base(__a) { }
> + explicit _Impl(_Alloc __a) noexcept : _M_a{std::move(__a)} { }
>
> - _Alloc& _M_alloc() noexcept { return _A_base::_S_get(*this); }
> + _Alloc& _M_alloc() noexcept { return _M_a._M_obj; }
>
> __gnu_cxx::__aligned_buffer<__remove_cv_t<_Tp>> _M_storage;
> };
> diff --git a/libstdc++-v3/include/std/tuple b/libstdc++-v3/include/std/tuple
> index f7caa79cda04..32800d8d7752 100644
> --- a/libstdc++-v3/include/std/tuple
> +++ b/libstdc++-v3/include/std/tuple
> @@ -68,7 +68,12 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> template<typename... _Elements>
> class tuple;
>
> +#if ! __has_cpp_attribute(__no_unique_address__)
> +#error "support for [[__no_unique_address__]] attribute is required"
> +#endif
> +
> /// @cond undocumented
> +#if ! _GLIBCXX_INLINE_VERSION
> template<typename _Tp>
> struct __is_empty_non_tuple : is_empty<_Tp> { };
>
> @@ -76,17 +81,21 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> template<typename _El0, typename... _El>
> struct __is_empty_non_tuple<tuple<_El0, _El...>> : false_type { };
>
> - // Use the Empty Base-class Optimization for empty, non-final types.
> + // Use [[no_unique_address]] for empty, non-final types.
> template<typename _Tp>
> using __empty_not_final
> = __conditional_t<__is_final(_Tp), false_type,
> __is_empty_non_tuple<_Tp>>;
> +#else
> + // For the unstable ABI we always use [[no_unique_address]].
> + template<typename>
> + using __empty_not_final = true_type;
> +#endif
>
> template<size_t _Idx, typename _Head,
> bool = __empty_not_final<_Head>::value>
> struct _Head_base;
>
> -#if __has_cpp_attribute(__no_unique_address__)
> template<size_t _Idx, typename _Head>
> struct _Head_base<_Idx, _Head, true>
> {
> @@ -141,61 +150,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>
> [[__no_unique_address__]] _Head _M_head_impl;
> };
> -#else
> - template<size_t _Idx, typename _Head>
> - struct _Head_base<_Idx, _Head, true>
> - : public _Head
> - {
> - constexpr _Head_base()
> - : _Head() { }
> -
> - constexpr _Head_base(const _Head& __h)
> - : _Head(__h) { }
> -
> - constexpr _Head_base(const _Head_base&) = default;
> - constexpr _Head_base(_Head_base&&) = default;
> -
> - template<typename _UHead>
> - constexpr _Head_base(_UHead&& __h)
> - : _Head(std::forward<_UHead>(__h)) { }
> -
> - _GLIBCXX20_CONSTEXPR
> - _Head_base(allocator_arg_t, __uses_alloc0)
> - : _Head() { }
> -
> - template<typename _Alloc>
> - _GLIBCXX20_CONSTEXPR
> - _Head_base(allocator_arg_t, __uses_alloc1<_Alloc> __a)
> - : _Head(allocator_arg, *__a._M_a) { }
> -
> - template<typename _Alloc>
> - _GLIBCXX20_CONSTEXPR
> - _Head_base(allocator_arg_t, __uses_alloc2<_Alloc> __a)
> - : _Head(*__a._M_a) { }
> -
> - template<typename _UHead>
> - _GLIBCXX20_CONSTEXPR
> - _Head_base(__uses_alloc0, _UHead&& __uhead)
> - : _Head(std::forward<_UHead>(__uhead)) { }
> -
> - template<typename _Alloc, typename _UHead>
> - _GLIBCXX20_CONSTEXPR
> - _Head_base(__uses_alloc1<_Alloc> __a, _UHead&& __uhead)
> - : _Head(allocator_arg, *__a._M_a, std::forward<_UHead>(__uhead)) { }
> -
> - template<typename _Alloc, typename _UHead>
> - _GLIBCXX20_CONSTEXPR
> - _Head_base(__uses_alloc2<_Alloc> __a, _UHead&& __uhead)
> - : _Head(std::forward<_UHead>(__uhead), *__a._M_a) { }
> -
> - static constexpr _Head&
> - _M_head(_Head_base& __b) noexcept { return __b; }
> -
> - static constexpr const _Head&
> - _M_head(const _Head_base& __b) noexcept { return __b; }
> - };
> -#endif
>
> +#if ! _GLIBCXX_INLINE_VERSION
> template<size_t _Idx, typename _Head>
> struct _Head_base<_Idx, _Head, false>
> {
> @@ -250,6 +206,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>
> _Head _M_head_impl;
> };
> +#endif
>
> #if __cpp_lib_tuple_like // >= C++23
> struct __tuple_like_tag_t { explicit __tuple_like_tag_t() = default; };
> --
> 2.54.0
>
On Tue, May 5, 2026 at 5:07 PM Jonathan Wakely <jwakely@redhat.com> wrote:
> Clang 9 added support for [[__no_unique_address__]] and we don't support
> Intel icc any longer, so we can remove the code in <tuple> that works
> around the absence of that attribute. We can also address a FIXME in
> <bits/shared_ptr_base.h> and replace uses of EBO with the attribute.
>
> libstdc++-v3/ChangeLog:
>
> * include/bits/shared_ptr_base.h (_Sp_ebo_helper): Simplify by
> using [[__no_unique_address__]] instead of EBO. Use the
> attribute unconditionally for the unstable ABI.
> (_Sp_counted_deleter::_Impl): Adjust uses of _Sp_ebo_helper.
> (_Sp_counted_ptr_inplace::_Impl): Likewise.
> * include/std/tuple (_Head_base): Remove implementation for
> compilers that don't support [[__no_unique_address__]]. Use the
> attribute unconditionally for the unstable ABI.
> ---
>
> Tested x86_64-linux.
>
> libstdc++-v3/include/bits/shared_ptr_base.h | 65 +++++++++----------
> libstdc++-v3/include/std/tuple | 69 ++++-----------------
> 2 files changed, 43 insertions(+), 91 deletions(-)
>
> diff --git a/libstdc++-v3/include/bits/shared_ptr_base.h
> b/libstdc++-v3/include/bits/shared_ptr_base.h
> index b92e3a4c90e4..3a966eddc06c 100644
> --- a/libstdc++-v3/include/bits/shared_ptr_base.h
> +++ b/libstdc++-v3/include/bits/shared_ptr_base.h
> @@ -513,57 +513,52 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> inline void
> _Sp_counted_ptr<nullptr_t, _S_atomic>::_M_dispose() noexcept { }
>
> - // FIXME: once __has_cpp_attribute(__no_unique_address__)) is true for
> - // all supported compilers we can greatly simplify _Sp_ebo_helper.
> +#if ! __has_cpp_attribute(__no_unique_address__)
> +#error "support for [[__no_unique_address__]] attribute is required"
> +#endif
> +
> +#if ! _GLIBCXX_INLINE_VERSION
> // N.B. unconditionally applying the attribute could change layout for
> // final types, which currently cannot use EBO so have a unique address.
> -
> - template<int _Nm, typename _Tp,
> - bool __use_ebo = !__is_final(_Tp) && __is_empty(_Tp)>
> + template<typename _Tp, bool = !__is_final(_Tp) && __is_empty(_Tp)>
> struct _Sp_ebo_helper;
> +#else
> + template<typename _Tp, bool = true>
> + struct _Sp_ebo_helper;
> +#endif
>
> - /// Specialization using EBO.
> - template<int _Nm, typename _Tp>
> - struct _Sp_ebo_helper<_Nm, _Tp, true> : private _Tp
> + /// Specialization using [[no_unique_address]].
> + template<typename _Tp>
> + struct _Sp_ebo_helper<_Tp, true>
> {
> - explicit _Sp_ebo_helper(const _Tp& __tp) : _Tp(__tp) { }
> - explicit _Sp_ebo_helper(_Tp&& __tp) : _Tp(std::move(__tp)) { }
> -
> - static _Tp&
> - _S_get(_Sp_ebo_helper& __eboh) { return static_cast<_Tp&>(__eboh); }
> + [[__no_unique_address__]] _Tp _M_obj;
> };
>
> - /// Specialization not using EBO.
> - template<int _Nm, typename _Tp>
> - struct _Sp_ebo_helper<_Nm, _Tp, false>
> +#if ! _GLIBCXX_INLINE_VERSION
> + /// Specialization not using [[no_unique_address]].
> + template<typename _Tp>
> + struct _Sp_ebo_helper<_Tp, false>
> {
> - explicit _Sp_ebo_helper(const _Tp& __tp) : _M_tp(__tp) { }
> - explicit _Sp_ebo_helper(_Tp&& __tp) : _M_tp(std::move(__tp)) { }
> -
> - static _Tp&
> - _S_get(_Sp_ebo_helper& __eboh)
> - { return __eboh._M_tp; }
> -
> - private:
> - _Tp _M_tp;
> + _Tp _M_obj;
> };
> +#endif
>
> // Support for custom deleter and/or allocator
> template<typename _Ptr, typename _Deleter, typename _Alloc,
> _Lock_policy _Lp>
> class _Sp_counted_deleter final : public _Sp_counted_base<_Lp>
> {
> - class _Impl : _Sp_ebo_helper<0, _Deleter>, _Sp_ebo_helper<1, _Alloc>
> + class _Impl
> {
> - typedef _Sp_ebo_helper<0, _Deleter> _Del_base;
> - typedef _Sp_ebo_helper<1, _Alloc> _Alloc_base;
> + _Sp_ebo_helper<_Deleter> _M_d;
> + _Sp_ebo_helper<_Alloc> _M_a;
>
I have wondered if that could change the data layout if _Deleter is same as
_Alloc
(which is handled by base being indexed), but the test showed that it is
the same:
https://godbolt.org/z/o8Y9s4be4
>
> public:
> _Impl(_Ptr __p, _Deleter __d, const _Alloc& __a) noexcept
> - : _Del_base(std::move(__d)), _Alloc_base(__a), _M_ptr(__p)
> + : _M_d{std::move(__d)}, _M_a{__a}, _M_ptr(__p)
> { }
>
> - _Deleter& _M_del() noexcept { return _Del_base::_S_get(*this); }
> - _Alloc& _M_alloc() noexcept { return _Alloc_base::_S_get(*this); }
> + _Deleter& _M_del() noexcept { return _M_d._M_obj; }
> + _Alloc& _M_alloc() noexcept { return _M_a._M_obj; }
>
> _Ptr _M_ptr;
> };
> @@ -645,14 +640,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> template<typename _Tp, typename _Alloc, _Lock_policy _Lp>
> class _Sp_counted_ptr_inplace final : public _Sp_counted_base<_Lp>
> {
> - class _Impl : _Sp_ebo_helper<0, _Alloc>
> + class _Impl
> {
> - typedef _Sp_ebo_helper<0, _Alloc> _A_base;
> + _Sp_ebo_helper<_Alloc> _M_a;
>
> public:
> - explicit _Impl(_Alloc __a) noexcept : _A_base(__a) { }
> + explicit _Impl(_Alloc __a) noexcept : _M_a{std::move(__a)} { }
>
> - _Alloc& _M_alloc() noexcept { return _A_base::_S_get(*this); }
> + _Alloc& _M_alloc() noexcept { return _M_a._M_obj; }
>
> __gnu_cxx::__aligned_buffer<__remove_cv_t<_Tp>> _M_storage;
> };
> diff --git a/libstdc++-v3/include/std/tuple
> b/libstdc++-v3/include/std/tuple
> index f7caa79cda04..32800d8d7752 100644
> --- a/libstdc++-v3/include/std/tuple
> +++ b/libstdc++-v3/include/std/tuple
> @@ -68,7 +68,12 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> template<typename... _Elements>
> class tuple;
>
> +#if ! __has_cpp_attribute(__no_unique_address__)
> +#error "support for [[__no_unique_address__]] attribute is required"
> +#endif
> +
> /// @cond undocumented
> +#if ! _GLIBCXX_INLINE_VERSION
> template<typename _Tp>
> struct __is_empty_non_tuple : is_empty<_Tp> { };
>
> @@ -76,17 +81,21 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> template<typename _El0, typename... _El>
> struct __is_empty_non_tuple<tuple<_El0, _El...>> : false_type { };
>
> - // Use the Empty Base-class Optimization for empty, non-final types.
> + // Use [[no_unique_address]] for empty, non-final types.
> template<typename _Tp>
> using __empty_not_final
> = __conditional_t<__is_final(_Tp), false_type,
> __is_empty_non_tuple<_Tp>>;
> +#else
> + // For the unstable ABI we always use [[no_unique_address]].
> + template<typename>
> + using __empty_not_final = true_type;
> +#endif
>
> template<size_t _Idx, typename _Head,
> bool = __empty_not_final<_Head>::value>
> struct _Head_base;
>
> -#if __has_cpp_attribute(__no_unique_address__)
> template<size_t _Idx, typename _Head>
> struct _Head_base<_Idx, _Head, true>
> {
> @@ -141,61 +150,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>
> [[__no_unique_address__]] _Head _M_head_impl;
> };
> -#else
> - template<size_t _Idx, typename _Head>
> - struct _Head_base<_Idx, _Head, true>
> - : public _Head
> - {
> - constexpr _Head_base()
> - : _Head() { }
> -
> - constexpr _Head_base(const _Head& __h)
> - : _Head(__h) { }
> -
> - constexpr _Head_base(const _Head_base&) = default;
> - constexpr _Head_base(_Head_base&&) = default;
> -
> - template<typename _UHead>
> - constexpr _Head_base(_UHead&& __h)
> - : _Head(std::forward<_UHead>(__h)) { }
> -
> - _GLIBCXX20_CONSTEXPR
> - _Head_base(allocator_arg_t, __uses_alloc0)
> - : _Head() { }
> -
> - template<typename _Alloc>
> - _GLIBCXX20_CONSTEXPR
> - _Head_base(allocator_arg_t, __uses_alloc1<_Alloc> __a)
> - : _Head(allocator_arg, *__a._M_a) { }
> -
> - template<typename _Alloc>
> - _GLIBCXX20_CONSTEXPR
> - _Head_base(allocator_arg_t, __uses_alloc2<_Alloc> __a)
> - : _Head(*__a._M_a) { }
> -
> - template<typename _UHead>
> - _GLIBCXX20_CONSTEXPR
> - _Head_base(__uses_alloc0, _UHead&& __uhead)
> - : _Head(std::forward<_UHead>(__uhead)) { }
> -
> - template<typename _Alloc, typename _UHead>
> - _GLIBCXX20_CONSTEXPR
> - _Head_base(__uses_alloc1<_Alloc> __a, _UHead&& __uhead)
> - : _Head(allocator_arg, *__a._M_a, std::forward<_UHead>(__uhead)) {
> }
> -
> - template<typename _Alloc, typename _UHead>
> - _GLIBCXX20_CONSTEXPR
> - _Head_base(__uses_alloc2<_Alloc> __a, _UHead&& __uhead)
> - : _Head(std::forward<_UHead>(__uhead), *__a._M_a) { }
> -
> - static constexpr _Head&
> - _M_head(_Head_base& __b) noexcept { return __b; }
> -
> - static constexpr const _Head&
> - _M_head(const _Head_base& __b) noexcept { return __b; }
> - };
> -#endif
>
> +#if ! _GLIBCXX_INLINE_VERSION
> template<size_t _Idx, typename _Head>
> struct _Head_base<_Idx, _Head, false>
> {
> @@ -250,6 +206,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>
> _Head _M_head_impl;
> };
> +#endif
>
> #if __cpp_lib_tuple_like // >= C++23
> struct __tuple_like_tag_t { explicit __tuple_like_tag_t() = default; };
> --
> 2.54.0
>
>
On Wed, 6 May 2026 at 08:54, Tomasz Kaminski <tkaminsk@redhat.com> wrote:
>
>
>
> On Tue, May 5, 2026 at 5:07 PM Jonathan Wakely <jwakely@redhat.com> wrote:
>>
>> Clang 9 added support for [[__no_unique_address__]] and we don't support
>> Intel icc any longer, so we can remove the code in <tuple> that works
>> around the absence of that attribute. We can also address a FIXME in
>> <bits/shared_ptr_base.h> and replace uses of EBO with the attribute.
>>
>> libstdc++-v3/ChangeLog:
>>
>> * include/bits/shared_ptr_base.h (_Sp_ebo_helper): Simplify by
>> using [[__no_unique_address__]] instead of EBO. Use the
>> attribute unconditionally for the unstable ABI.
>> (_Sp_counted_deleter::_Impl): Adjust uses of _Sp_ebo_helper.
>> (_Sp_counted_ptr_inplace::_Impl): Likewise.
>> * include/std/tuple (_Head_base): Remove implementation for
>> compilers that don't support [[__no_unique_address__]]. Use the
>> attribute unconditionally for the unstable ABI.
>> ---
>>
>> Tested x86_64-linux.
>>
>> libstdc++-v3/include/bits/shared_ptr_base.h | 65 +++++++++----------
>> libstdc++-v3/include/std/tuple | 69 ++++-----------------
>> 2 files changed, 43 insertions(+), 91 deletions(-)
>>
>> diff --git a/libstdc++-v3/include/bits/shared_ptr_base.h b/libstdc++-v3/include/bits/shared_ptr_base.h
>> index b92e3a4c90e4..3a966eddc06c 100644
>> --- a/libstdc++-v3/include/bits/shared_ptr_base.h
>> +++ b/libstdc++-v3/include/bits/shared_ptr_base.h
>> @@ -513,57 +513,52 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>> inline void
>> _Sp_counted_ptr<nullptr_t, _S_atomic>::_M_dispose() noexcept { }
>>
>> - // FIXME: once __has_cpp_attribute(__no_unique_address__)) is true for
>> - // all supported compilers we can greatly simplify _Sp_ebo_helper.
>> +#if ! __has_cpp_attribute(__no_unique_address__)
>> +#error "support for [[__no_unique_address__]] attribute is required"
>> +#endif
>> +
>> +#if ! _GLIBCXX_INLINE_VERSION
>> // N.B. unconditionally applying the attribute could change layout for
>> // final types, which currently cannot use EBO so have a unique address.
>> -
>> - template<int _Nm, typename _Tp,
>> - bool __use_ebo = !__is_final(_Tp) && __is_empty(_Tp)>
>> + template<typename _Tp, bool = !__is_final(_Tp) && __is_empty(_Tp)>
>> struct _Sp_ebo_helper;
>> +#else
>> + template<typename _Tp, bool = true>
>> + struct _Sp_ebo_helper;
>> +#endif
>>
>> - /// Specialization using EBO.
>> - template<int _Nm, typename _Tp>
>> - struct _Sp_ebo_helper<_Nm, _Tp, true> : private _Tp
>> + /// Specialization using [[no_unique_address]].
>> + template<typename _Tp>
>> + struct _Sp_ebo_helper<_Tp, true>
>> {
>> - explicit _Sp_ebo_helper(const _Tp& __tp) : _Tp(__tp) { }
>> - explicit _Sp_ebo_helper(_Tp&& __tp) : _Tp(std::move(__tp)) { }
>> -
>> - static _Tp&
>> - _S_get(_Sp_ebo_helper& __eboh) { return static_cast<_Tp&>(__eboh); }
>> + [[__no_unique_address__]] _Tp _M_obj;
>> };
>>
>> - /// Specialization not using EBO.
>> - template<int _Nm, typename _Tp>
>> - struct _Sp_ebo_helper<_Nm, _Tp, false>
>> +#if ! _GLIBCXX_INLINE_VERSION
>> + /// Specialization not using [[no_unique_address]].
>> + template<typename _Tp>
>> + struct _Sp_ebo_helper<_Tp, false>
>> {
>> - explicit _Sp_ebo_helper(const _Tp& __tp) : _M_tp(__tp) { }
>> - explicit _Sp_ebo_helper(_Tp&& __tp) : _M_tp(std::move(__tp)) { }
>> -
>> - static _Tp&
>> - _S_get(_Sp_ebo_helper& __eboh)
>> - { return __eboh._M_tp; }
>> -
>> - private:
>> - _Tp _M_tp;
>> + _Tp _M_obj;
>> };
>> +#endif
>>
>> // Support for custom deleter and/or allocator
>> template<typename _Ptr, typename _Deleter, typename _Alloc, _Lock_policy _Lp>
>> class _Sp_counted_deleter final : public _Sp_counted_base<_Lp>
>> {
>> - class _Impl : _Sp_ebo_helper<0, _Deleter>, _Sp_ebo_helper<1, _Alloc>
>> + class _Impl
>> {
>> - typedef _Sp_ebo_helper<0, _Deleter> _Del_base;
>> - typedef _Sp_ebo_helper<1, _Alloc> _Alloc_base;
>> + _Sp_ebo_helper<_Deleter> _M_d;
>> + _Sp_ebo_helper<_Alloc> _M_a;
>
> I have wondered if that could change the data layout if _Deleter is same as _Alloc
> (which is handled by base being indexed), but the test showed that it is the same:
> https://godbolt.org/z/o8Y9s4be4
Right, but it does need the attribute on both these members, to make
_M_d._M_obj and _M_a._M_obj potentially overlapping.
https://godbolt.org/z/s1veK5jG9
Patch v2 incoming ...
>>
>>
>> public:
>> _Impl(_Ptr __p, _Deleter __d, const _Alloc& __a) noexcept
>> - : _Del_base(std::move(__d)), _Alloc_base(__a), _M_ptr(__p)
>> + : _M_d{std::move(__d)}, _M_a{__a}, _M_ptr(__p)
>> { }
>>
>> - _Deleter& _M_del() noexcept { return _Del_base::_S_get(*this); }
>> - _Alloc& _M_alloc() noexcept { return _Alloc_base::_S_get(*this); }
>> + _Deleter& _M_del() noexcept { return _M_d._M_obj; }
>> + _Alloc& _M_alloc() noexcept { return _M_a._M_obj; }
>>
>> _Ptr _M_ptr;
>> };
>> @@ -645,14 +640,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>> template<typename _Tp, typename _Alloc, _Lock_policy _Lp>
>> class _Sp_counted_ptr_inplace final : public _Sp_counted_base<_Lp>
>> {
>> - class _Impl : _Sp_ebo_helper<0, _Alloc>
>> + class _Impl
>> {
>> - typedef _Sp_ebo_helper<0, _Alloc> _A_base;
>> + _Sp_ebo_helper<_Alloc> _M_a;
>>
>> public:
>> - explicit _Impl(_Alloc __a) noexcept : _A_base(__a) { }
>> + explicit _Impl(_Alloc __a) noexcept : _M_a{std::move(__a)} { }
>>
>> - _Alloc& _M_alloc() noexcept { return _A_base::_S_get(*this); }
>> + _Alloc& _M_alloc() noexcept { return _M_a._M_obj; }
>>
>> __gnu_cxx::__aligned_buffer<__remove_cv_t<_Tp>> _M_storage;
>> };
>> diff --git a/libstdc++-v3/include/std/tuple b/libstdc++-v3/include/std/tuple
>> index f7caa79cda04..32800d8d7752 100644
>> --- a/libstdc++-v3/include/std/tuple
>> +++ b/libstdc++-v3/include/std/tuple
>> @@ -68,7 +68,12 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>> template<typename... _Elements>
>> class tuple;
>>
>> +#if ! __has_cpp_attribute(__no_unique_address__)
>> +#error "support for [[__no_unique_address__]] attribute is required"
>> +#endif
>> +
>> /// @cond undocumented
>> +#if ! _GLIBCXX_INLINE_VERSION
>> template<typename _Tp>
>> struct __is_empty_non_tuple : is_empty<_Tp> { };
>>
>> @@ -76,17 +81,21 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>> template<typename _El0, typename... _El>
>> struct __is_empty_non_tuple<tuple<_El0, _El...>> : false_type { };
>>
>> - // Use the Empty Base-class Optimization for empty, non-final types.
>> + // Use [[no_unique_address]] for empty, non-final types.
>> template<typename _Tp>
>> using __empty_not_final
>> = __conditional_t<__is_final(_Tp), false_type,
>> __is_empty_non_tuple<_Tp>>;
>> +#else
>> + // For the unstable ABI we always use [[no_unique_address]].
>> + template<typename>
>> + using __empty_not_final = true_type;
>> +#endif
>>
>> template<size_t _Idx, typename _Head,
>> bool = __empty_not_final<_Head>::value>
>> struct _Head_base;
>>
>> -#if __has_cpp_attribute(__no_unique_address__)
>> template<size_t _Idx, typename _Head>
>> struct _Head_base<_Idx, _Head, true>
>> {
>> @@ -141,61 +150,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>>
>> [[__no_unique_address__]] _Head _M_head_impl;
>> };
>> -#else
>> - template<size_t _Idx, typename _Head>
>> - struct _Head_base<_Idx, _Head, true>
>> - : public _Head
>> - {
>> - constexpr _Head_base()
>> - : _Head() { }
>> -
>> - constexpr _Head_base(const _Head& __h)
>> - : _Head(__h) { }
>> -
>> - constexpr _Head_base(const _Head_base&) = default;
>> - constexpr _Head_base(_Head_base&&) = default;
>> -
>> - template<typename _UHead>
>> - constexpr _Head_base(_UHead&& __h)
>> - : _Head(std::forward<_UHead>(__h)) { }
>> -
>> - _GLIBCXX20_CONSTEXPR
>> - _Head_base(allocator_arg_t, __uses_alloc0)
>> - : _Head() { }
>> -
>> - template<typename _Alloc>
>> - _GLIBCXX20_CONSTEXPR
>> - _Head_base(allocator_arg_t, __uses_alloc1<_Alloc> __a)
>> - : _Head(allocator_arg, *__a._M_a) { }
>> -
>> - template<typename _Alloc>
>> - _GLIBCXX20_CONSTEXPR
>> - _Head_base(allocator_arg_t, __uses_alloc2<_Alloc> __a)
>> - : _Head(*__a._M_a) { }
>> -
>> - template<typename _UHead>
>> - _GLIBCXX20_CONSTEXPR
>> - _Head_base(__uses_alloc0, _UHead&& __uhead)
>> - : _Head(std::forward<_UHead>(__uhead)) { }
>> -
>> - template<typename _Alloc, typename _UHead>
>> - _GLIBCXX20_CONSTEXPR
>> - _Head_base(__uses_alloc1<_Alloc> __a, _UHead&& __uhead)
>> - : _Head(allocator_arg, *__a._M_a, std::forward<_UHead>(__uhead)) { }
>> -
>> - template<typename _Alloc, typename _UHead>
>> - _GLIBCXX20_CONSTEXPR
>> - _Head_base(__uses_alloc2<_Alloc> __a, _UHead&& __uhead)
>> - : _Head(std::forward<_UHead>(__uhead), *__a._M_a) { }
>> -
>> - static constexpr _Head&
>> - _M_head(_Head_base& __b) noexcept { return __b; }
>> -
>> - static constexpr const _Head&
>> - _M_head(const _Head_base& __b) noexcept { return __b; }
>> - };
>> -#endif
>>
>> +#if ! _GLIBCXX_INLINE_VERSION
>> template<size_t _Idx, typename _Head>
>> struct _Head_base<_Idx, _Head, false>
>> {
>> @@ -250,6 +206,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>>
>> _Head _M_head_impl;
>> };
>> +#endif
>>
>> #if __cpp_lib_tuple_like // >= C++23
>> struct __tuple_like_tag_t { explicit __tuple_like_tag_t() = default; };
>> --
>> 2.54.0
>>
@@ -513,57 +513,52 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
inline void
_Sp_counted_ptr<nullptr_t, _S_atomic>::_M_dispose() noexcept { }
- // FIXME: once __has_cpp_attribute(__no_unique_address__)) is true for
- // all supported compilers we can greatly simplify _Sp_ebo_helper.
+#if ! __has_cpp_attribute(__no_unique_address__)
+#error "support for [[__no_unique_address__]] attribute is required"
+#endif
+
+#if ! _GLIBCXX_INLINE_VERSION
// N.B. unconditionally applying the attribute could change layout for
// final types, which currently cannot use EBO so have a unique address.
-
- template<int _Nm, typename _Tp,
- bool __use_ebo = !__is_final(_Tp) && __is_empty(_Tp)>
+ template<typename _Tp, bool = !__is_final(_Tp) && __is_empty(_Tp)>
struct _Sp_ebo_helper;
+#else
+ template<typename _Tp, bool = true>
+ struct _Sp_ebo_helper;
+#endif
- /// Specialization using EBO.
- template<int _Nm, typename _Tp>
- struct _Sp_ebo_helper<_Nm, _Tp, true> : private _Tp
+ /// Specialization using [[no_unique_address]].
+ template<typename _Tp>
+ struct _Sp_ebo_helper<_Tp, true>
{
- explicit _Sp_ebo_helper(const _Tp& __tp) : _Tp(__tp) { }
- explicit _Sp_ebo_helper(_Tp&& __tp) : _Tp(std::move(__tp)) { }
-
- static _Tp&
- _S_get(_Sp_ebo_helper& __eboh) { return static_cast<_Tp&>(__eboh); }
+ [[__no_unique_address__]] _Tp _M_obj;
};
- /// Specialization not using EBO.
- template<int _Nm, typename _Tp>
- struct _Sp_ebo_helper<_Nm, _Tp, false>
+#if ! _GLIBCXX_INLINE_VERSION
+ /// Specialization not using [[no_unique_address]].
+ template<typename _Tp>
+ struct _Sp_ebo_helper<_Tp, false>
{
- explicit _Sp_ebo_helper(const _Tp& __tp) : _M_tp(__tp) { }
- explicit _Sp_ebo_helper(_Tp&& __tp) : _M_tp(std::move(__tp)) { }
-
- static _Tp&
- _S_get(_Sp_ebo_helper& __eboh)
- { return __eboh._M_tp; }
-
- private:
- _Tp _M_tp;
+ _Tp _M_obj;
};
+#endif
// Support for custom deleter and/or allocator
template<typename _Ptr, typename _Deleter, typename _Alloc, _Lock_policy _Lp>
class _Sp_counted_deleter final : public _Sp_counted_base<_Lp>
{
- class _Impl : _Sp_ebo_helper<0, _Deleter>, _Sp_ebo_helper<1, _Alloc>
+ class _Impl
{
- typedef _Sp_ebo_helper<0, _Deleter> _Del_base;
- typedef _Sp_ebo_helper<1, _Alloc> _Alloc_base;
+ _Sp_ebo_helper<_Deleter> _M_d;
+ _Sp_ebo_helper<_Alloc> _M_a;
public:
_Impl(_Ptr __p, _Deleter __d, const _Alloc& __a) noexcept
- : _Del_base(std::move(__d)), _Alloc_base(__a), _M_ptr(__p)
+ : _M_d{std::move(__d)}, _M_a{__a}, _M_ptr(__p)
{ }
- _Deleter& _M_del() noexcept { return _Del_base::_S_get(*this); }
- _Alloc& _M_alloc() noexcept { return _Alloc_base::_S_get(*this); }
+ _Deleter& _M_del() noexcept { return _M_d._M_obj; }
+ _Alloc& _M_alloc() noexcept { return _M_a._M_obj; }
_Ptr _M_ptr;
};
@@ -645,14 +640,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
template<typename _Tp, typename _Alloc, _Lock_policy _Lp>
class _Sp_counted_ptr_inplace final : public _Sp_counted_base<_Lp>
{
- class _Impl : _Sp_ebo_helper<0, _Alloc>
+ class _Impl
{
- typedef _Sp_ebo_helper<0, _Alloc> _A_base;
+ _Sp_ebo_helper<_Alloc> _M_a;
public:
- explicit _Impl(_Alloc __a) noexcept : _A_base(__a) { }
+ explicit _Impl(_Alloc __a) noexcept : _M_a{std::move(__a)} { }
- _Alloc& _M_alloc() noexcept { return _A_base::_S_get(*this); }
+ _Alloc& _M_alloc() noexcept { return _M_a._M_obj; }
__gnu_cxx::__aligned_buffer<__remove_cv_t<_Tp>> _M_storage;
};
@@ -68,7 +68,12 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
template<typename... _Elements>
class tuple;
+#if ! __has_cpp_attribute(__no_unique_address__)
+#error "support for [[__no_unique_address__]] attribute is required"
+#endif
+
/// @cond undocumented
+#if ! _GLIBCXX_INLINE_VERSION
template<typename _Tp>
struct __is_empty_non_tuple : is_empty<_Tp> { };
@@ -76,17 +81,21 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
template<typename _El0, typename... _El>
struct __is_empty_non_tuple<tuple<_El0, _El...>> : false_type { };
- // Use the Empty Base-class Optimization for empty, non-final types.
+ // Use [[no_unique_address]] for empty, non-final types.
template<typename _Tp>
using __empty_not_final
= __conditional_t<__is_final(_Tp), false_type,
__is_empty_non_tuple<_Tp>>;
+#else
+ // For the unstable ABI we always use [[no_unique_address]].
+ template<typename>
+ using __empty_not_final = true_type;
+#endif
template<size_t _Idx, typename _Head,
bool = __empty_not_final<_Head>::value>
struct _Head_base;
-#if __has_cpp_attribute(__no_unique_address__)
template<size_t _Idx, typename _Head>
struct _Head_base<_Idx, _Head, true>
{
@@ -141,61 +150,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
[[__no_unique_address__]] _Head _M_head_impl;
};
-#else
- template<size_t _Idx, typename _Head>
- struct _Head_base<_Idx, _Head, true>
- : public _Head
- {
- constexpr _Head_base()
- : _Head() { }
-
- constexpr _Head_base(const _Head& __h)
- : _Head(__h) { }
-
- constexpr _Head_base(const _Head_base&) = default;
- constexpr _Head_base(_Head_base&&) = default;
-
- template<typename _UHead>
- constexpr _Head_base(_UHead&& __h)
- : _Head(std::forward<_UHead>(__h)) { }
-
- _GLIBCXX20_CONSTEXPR
- _Head_base(allocator_arg_t, __uses_alloc0)
- : _Head() { }
-
- template<typename _Alloc>
- _GLIBCXX20_CONSTEXPR
- _Head_base(allocator_arg_t, __uses_alloc1<_Alloc> __a)
- : _Head(allocator_arg, *__a._M_a) { }
-
- template<typename _Alloc>
- _GLIBCXX20_CONSTEXPR
- _Head_base(allocator_arg_t, __uses_alloc2<_Alloc> __a)
- : _Head(*__a._M_a) { }
-
- template<typename _UHead>
- _GLIBCXX20_CONSTEXPR
- _Head_base(__uses_alloc0, _UHead&& __uhead)
- : _Head(std::forward<_UHead>(__uhead)) { }
-
- template<typename _Alloc, typename _UHead>
- _GLIBCXX20_CONSTEXPR
- _Head_base(__uses_alloc1<_Alloc> __a, _UHead&& __uhead)
- : _Head(allocator_arg, *__a._M_a, std::forward<_UHead>(__uhead)) { }
-
- template<typename _Alloc, typename _UHead>
- _GLIBCXX20_CONSTEXPR
- _Head_base(__uses_alloc2<_Alloc> __a, _UHead&& __uhead)
- : _Head(std::forward<_UHead>(__uhead), *__a._M_a) { }
-
- static constexpr _Head&
- _M_head(_Head_base& __b) noexcept { return __b; }
-
- static constexpr const _Head&
- _M_head(const _Head_base& __b) noexcept { return __b; }
- };
-#endif
+#if ! _GLIBCXX_INLINE_VERSION
template<size_t _Idx, typename _Head>
struct _Head_base<_Idx, _Head, false>
{
@@ -250,6 +206,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
_Head _M_head_impl;
};
+#endif
#if __cpp_lib_tuple_like // >= C++23
struct __tuple_like_tag_t { explicit __tuple_like_tag_t() = default; };