On Wed, 6 May 2026 at 13:37, Tomasz Kaminski <[email protected]> wrote:
>
>
>
> On Wed, May 6, 2026 at 2:13 PM Jonathan Wakely <[email protected]> wrote:
>>
>> On Wed, 6 May 2026 at 12:52, Tomasz Kaminski <[email protected]> wrote:
>> >
>> >
>> >
>> > On Wed, May 6, 2026 at 12:13 PM Jonathan Wakely <[email protected]> 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() { }
>>

Reply via email to