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