On Wed, 6 May 2026 at 08:54, Tomasz Kaminski <[email protected]> wrote: > > > > On Tue, May 5, 2026 at 5:07 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. >> --- >> >> 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 >>
