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