[v2,1/3] libstdc++: Replace uses of EBO with [[no_unique_address]]

Message ID 20260506085339.325517-1-jwakely@redhat.com
State New
Headers
Series [v2,1/3] libstdc++: Replace uses of EBO with [[no_unique_address]] |

Commit Message

Jonathan Wakely May 6, 2026, 8:48 a.m. UTC
  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.

 libstdc++-v3/include/bits/shared_ptr_base.h | 65 +++++++++----------
 libstdc++-v3/include/std/tuple              | 69 ++++-----------------
 2 files changed, 43 insertions(+), 91 deletions(-)
  

Comments

Tomasz Kaminski May 6, 2026, 11:52 a.m. UTC | #1
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.

> +#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.

>
> -#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
>
Nice improvment. Patrick once explained to me. that partial specialization
matching
is linear in their number, so this may help a lot.

>    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
>
>
  
Jonathan Wakely May 6, 2026, 12:12 p.m. UTC | #2
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.

We could even go one step further and remove the bool parameter for
the unstable ABI, to reduce the length of the symbol names:

#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() { }
  
Jonathan Wakely May 6, 2026, 12:14 p.m. UTC | #3
On Wed, 6 May 2026 at 13:12, 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

Oops, I meant "_Allow_overlapping" not Alloc.

> work, but is longer and unless you already understand what that means,
> it isn't any more understandable than "EBO helper".
>
  
Tomasz Kaminski May 6, 2026, 12:36 p.m. UTC | #4
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() { }
>
>
  
Jonathan Wakely May 6, 2026, 12:45 p.m. UTC | #5
On Wed, 6 May 2026 at 13:37, Tomasz Kaminski <tkaminsk@redhat.com> wrote:
>
>
>
> 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.

Yeah, it's only two bytes, although it's two bytes for each tuple
element, so it adds up across different specializations of std::tuple.

But we can make that change later if we want to, the unstable ABI is unstable.


>>
>>
>> #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() { }
>>
  

Patch

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;
+#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;
 
-#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; };