| Message ID | 20260506085339.325517-1-jwakely@redhat.com |
|---|---|
| State | New |
| Headers |
Return-Path: <gcc-patches-bounces~patchwork=sourceware.org@gcc.gnu.org> X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 8A3514BA23F7 for <patchwork@sourceware.org>; Wed, 6 May 2026 08:54:13 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 8A3514BA23F7 Authentication-Results: sourceware.org; dkim=pass (1024-bit key, unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=Xzjmk6qS X-Original-To: gcc-patches@gcc.gnu.org Delivered-To: gcc-patches@gcc.gnu.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by sourceware.org (Postfix) with ESMTP id 11AE04BA23FD for <gcc-patches@gcc.gnu.org>; Wed, 6 May 2026 08:53:46 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 11AE04BA23FD Authentication-Results: sourceware.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 11AE04BA23FD Authentication-Results: sourceware.org; arc=none smtp.remote-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1778057627; cv=none; b=Sd7Pq2bDRjThe7FbkMG1XZEgv3Fy2/cE7bxiKIkHdqf1YC6Hl42coSprvnUmve7bOipW+JB6vhfrJjBzooiELapWWfsadJ4iAfpqCVCcIbdKxzN0WVLX8tHONv5XkCPPv/faFVKW40/ejb5YxiCIxS2YamXCHehRAGR6t1DDMS4= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1778057627; c=relaxed/simple; bh=B7pDEpr0+fkrXRtxVSqbwRtsjOYndYyaWZ68RTI5KuM=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=AEEHjtot4z0mQxfpj1GXLfb4ku2ddeTQt5sY9nZybb+cmOYHM+oR0tNQczOmkzq2kP/pbVBB3RUAE7cM5v4xPzdD69hykiQwSI0vygb5yj+eNO4xe3cm+mlbaWQviblR+sPrLEkQWE+fRBsv630TcBgvJ+FkQyI75nb2rN0eNZA= ARC-Authentication-Results: i=1; sourceware.org; dkim=pass (1024-bit key, unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=Xzjmk6qS DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 11AE04BA23FD DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1778057626; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=/gLvmsU0C5sSPNYR4NT1HbrD/Fz+TQVbORtEDShrjVU=; b=Xzjmk6qSG7dQqhVcMTPxPw8uv5U1+LaowORajT9OMvo9DowoUbWKkI4YfOYd0og+a/k6Gc Rv2WhaRdZbwbVBArrE9fHPfSZbD0K9FxrI2/Sq6SNO1BRQM5hpQMxcHCPFoTJG3SvjpALv jDmIxn5ZgrhugCTaWwUj3yYXTSn48Vw= Received: from mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-410-yyYHEzrUMdqpa33Qp0BpyQ-1; Wed, 06 May 2026 04:53:43 -0400 X-MC-Unique: yyYHEzrUMdqpa33Qp0BpyQ-1 X-Mimecast-MFC-AGG-ID: yyYHEzrUMdqpa33Qp0BpyQ_1778057622 Received: from mx-prod-int-08.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-08.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.111]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id EF8EB18005B3; Wed, 6 May 2026 08:53:41 +0000 (UTC) Received: from zen.kayari.org (unknown [10.44.32.38]) by mx-prod-int-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id EE1C91800240; Wed, 6 May 2026 08:53:40 +0000 (UTC) From: Jonathan Wakely <jwakely@redhat.com> To: gcc-patches@gcc.gnu.org, libstdc++@gcc.gnu.org Subject: [PATCH v2 1/3] libstdc++: Replace uses of EBO with [[no_unique_address]] Date: Wed, 6 May 2026 09:48:04 +0100 Message-ID: <20260506085339.325517-1-jwakely@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.111 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: WNozS6X0qXxEdT-DpK5j1oR533F0x_1ylY8JgemkDVk_1778057622 X-Mimecast-Originator: redhat.com Content-Type: text/plain Content-Transfer-Encoding: 8bit X-Spam-Status: No, score=-12.4 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, PROLO_LEO1, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H5, RCVD_IN_MSPIKE_WL, SPF_HELO_PASS, SPF_NONE, TXREP autolearn=unavailable autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on sourceware.org X-BeenThere: gcc-patches@gcc.gnu.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Gcc-patches mailing list <gcc-patches.gcc.gnu.org> List-Unsubscribe: <https://gcc.gnu.org/mailman/options/gcc-patches>, <mailto:gcc-patches-request@gcc.gnu.org?subject=unsubscribe> List-Archive: <https://gcc.gnu.org/pipermail/gcc-patches/> List-Post: <mailto:gcc-patches@gcc.gnu.org> List-Help: <mailto:gcc-patches-request@gcc.gnu.org?subject=help> List-Subscribe: <https://gcc.gnu.org/mailman/listinfo/gcc-patches>, <mailto:gcc-patches-request@gcc.gnu.org?subject=subscribe> Errors-To: gcc-patches-bounces~patchwork=sourceware.org@gcc.gnu.org |
| 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
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 > >
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() { }
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". >
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() { } > >
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() { } >>
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; };