[PATCH v2 1/3] libstdc++: Replace uses of EBO with [[no_unique_address]]
Tomasz Kaminski
tkaminsk@redhat.com
Wed May 6 12:36:59 GMT 2026
On Wed, May 6, 2026 at 2:13 PM Jonathan Wakely <jwakely@redhat.com> wrote:
> On Wed, 6 May 2026 at 12:52, Tomasz Kaminski <tkaminsk@redhat.com> wrote:
> >
> >
> >
> > On Wed, May 6, 2026 at 12:13 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.
> >> ---
> >>
> >> v2: Add the attribute to the data members of _Impl as well as to the
> >> _M_obj data member of _Sp_ebo_helper. Otherwise the _M_obj subobject is
> >> potentially overlapping, but the _M_ and _M_d ones are not. We need both
> >> to be marked with the attribute.
> >>
> >> What we *really* want is [[no_unique_address(expr)]] so that we can get
> >> rid of _Sp_ebo_helper entirely, and just do:
> >>
> >> #if ! _GLIBCXX_INLINE_VERSION // Stable ABI
> >> template<typename T> __can_overlap = !__is_final(T) && __is_empty(T);
> >> #else // Unstable ABI
> >> template<typename T> __can_overlap = true;
> >> #endif
> >> [[no_unique_address(__can_overlap<_Del>)]] _M_del;
> >>
> >> I should propose that to WG21, and if rejected just get it added to GCC
> >> and Clang.
> >
> >
> >>
> >>
> >> Tested x86_64-linux.
> >
> > LGTM with one suggestion.
> >>
> >>
> >> 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..3ab73f6e4a0d 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;
> >
> > The name "EBO helper" no longer seems accurate, but
> > I have no better suggestion.
>
> Yes, I had the same thought. Something like "_Alloc_overlapping" could
> work, but is longer and unless you already understand what that means,
> it isn't any more understandable than "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;
> >> + [[__no_unique_address__]] _Sp_ebo_helper<_Deleter> _M_d;
> >> + [[__no_unique_address__]] _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;
> >> + [[__no_unique_address__]] _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;
> >
> > We forward declare _Head_base anyway, so maybe instead of
> __empty_not_final
> > we should have two declarations: one that defaults to __conditional_t
> and another.
> > that uses = true, as for _Sp_ebo_base. There seem to be non need for
> additional
> > class template.
>
> So something like this:
>
> #if ! _GLIBCXX_INLINE_VERSION
> template<typename _Tp>
> struct __is_empty_non_tuple : is_empty<_Tp> { };
>
> // Using EBO for elements that are tuples causes ambiguous base errors.
> template<typename _El0, typename... _El>
> struct __is_empty_non_tuple<tuple<_El0, _El...>> : false_type { };
>
> // 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>>;
>
> template<size_t _Idx, typename _Head,
> bool = __empty_not_final<_Head>::value>
>
struct _Head_base;
> #else
> // For the unstable ABI we always use [[no_unique_address]].
> template<size_t _Idx, typename _Head, bool = true>
> struct _Head_base;
> #endif
>
> template<size_t _Idx, typename _Head, bool>
> struct _Head_base
> {
>
> So instead of two partial specializations (for true/false) we would
> have the primary template and the partial specialization for false.
> For the unstable ABI, we would have only the primary template, so no
> partial specialization to match.
Yes, even better.
I would add bool /* true */> in the template head for primary
specialization,
So, it indicates when it is used.
>
> We could even go one step further and remove the bool parameter for
> the unstable ABI, to reduce the length of the symbol names:
>
The bool parameter is mangled as "b1", so I do not think there is so much
gain.
>
> #if ! _GLIBCXX_INLINE_VERSION
> ...
>
> template<size_t _Idx, typename _Head,
> bool = __empty_not_final<_Head>::value>
> struct _Head_base
> #else
> // For the unstable ABI we always use [[no_unique_address]].
> template<size_t _Idx, typename _Head>
> struct _Head_base
> #endif
> {
> constexpr _Head_base()
> : _M_head_impl() { }
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://gcc.gnu.org/pipermail/libstdc++/attachments/20260506/9ce8cf55/attachment-0001.htm>
More information about the Libstdc++
mailing list