From patchwork Thu Aug 1 21:32:27 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jonathan Wakely X-Patchwork-Id: 1968054 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=WFDoGZ0q; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=gcc.gnu.org (client-ip=8.43.85.97; helo=server2.sourceware.org; envelope-from=gcc-patches-bounces~incoming=patchwork.ozlabs.org@gcc.gnu.org; receiver=patchwork.ozlabs.org) Received: from server2.sourceware.org (server2.sourceware.org [8.43.85.97]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (secp384r1) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4WZj926NNHz1yZl for ; Fri, 2 Aug 2024 07:41:58 +1000 (AEST) Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 1C371385E441 for ; Thu, 1 Aug 2024 21:41:57 +0000 (GMT) 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 CEFC73858D35 for ; Thu, 1 Aug 2024 21:39:37 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org CEFC73858D35 Authentication-Results: sourceware.org; dmarc=pass (p=none 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 CEFC73858D35 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1722548382; cv=none; b=wnhVCBT+23PEwM6/Z5285AdiHwCkgMmvYLdHpdIy2LOFFjiaSTJC+9gyZ7wW3ZmZPsJt8HU3N3m8+Ba0rZrMV7ZfflNvg8A/oVxAaw6dCs7p0EXEjSajHa1g4j+3/mYqYwr/8z9FtlC46LfM3d7rpkVT6f62g2aMNF28P0DQJpM= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1722548382; c=relaxed/simple; bh=qg6bgE6wEsoyRjuRqEsB/CL6BAv5eFvHBpZ8aRIsH2E=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=wtHKecHXwahzR7ZFLbqA+czx7SDkhNO3GXsp++7vcGtJMte6q86YAl8iU6y7bkF7r5GlnE95rRhyXWWVK1yweuKrjiK0DxwpRl1ME4Q/dhsmXp7Gr9GctTtR5O3rADQ5nkw4MBqmzNqMDrguzq8rkhncQLn6ZVWOYRcTVmd4KLo= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1722548377; 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=y2+wecAbEQ5IiVW9VpRFzDvTelcc6Kq1hipLJlwk7Ig=; b=WFDoGZ0qm+Rt16tyGSkxLTaIsBbUE0FlXuxOKOvgBBgxHd0tBM6ZWKGL8pHuKS5eFDfUTV cFVSkxXrFhpJ8ZQeaVxxmG8gDcpJ9cw/GNRn/Byw+Y2mai51hMbakGtwyXxQjK9OujSfNF eYHKwo76CJtREFL1lmcz6EM/V7H5G/w= Received: from mx-prod-mc-04.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-536-aeJ8y_zsNcWnTJWv0hIAXQ-1; Thu, 01 Aug 2024 17:39:34 -0400 X-MC-Unique: aeJ8y_zsNcWnTJWv0hIAXQ-1 Received: from mx-prod-int-02.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-02.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.15]) (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-04.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 460FB1955D4C; Thu, 1 Aug 2024 21:39:33 +0000 (UTC) Received: from localhost (unknown [10.42.28.21]) by mx-prod-int-02.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 954CE1955D42; Thu, 1 Aug 2024 21:39:31 +0000 (UTC) From: Jonathan Wakely To: libstdc++@gcc.gnu.org, gcc-patches@gcc.gnu.org Subject: [PATCH] libstdc++: Fix std::allocator_traits::construct constraints [PR108619] Date: Thu, 1 Aug 2024 22:32:27 +0100 Message-ID: <20240801213927.388966-2-jwakely@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.0 on 10.30.177.15 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com X-Spam-Status: No, score=-12.0 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H4, RCVD_IN_MSPIKE_WL, SPF_HELO_NONE, SPF_NONE, TXREP, URI_HEX autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org X-BeenThere: gcc-patches@gcc.gnu.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Gcc-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: gcc-patches-bounces~incoming=patchwork.ozlabs.org@gcc.gnu.org The latest in the series of "rewrite all our enable_if-based overload sets using concepts!" improvements. A smaller change will be needed on the branches, this is just for trunk. Tested x86_64-linux. -- >8 -- Using std::is_constructible in the constraints introduces a spurious dependency on the type being destructible, which should not be required for constructing with an allocator. The test case shows a case where the type has a private destructor, which can be destroyed by the allocator, but std::is_destructible and std::is_constructible are false. Similarly, using is_nothrow_constructible in the noexcept-specifiers for the construct members of allocator_traits and std::allocator, __gnu_cxx::__new_allocator, and __gnu_cxx::__malloc_allocator gives the wrong answer if the type isn't destructible. We need a new type trait to define those correctly, so that we only check if the placement new-expression is nothrow after using is_constructible to check that it would be well-formed. Instead of just fixing the overly restrictive constraint to check for placement new, rewrite allocator_traits in terms of 'if constexpr' using variable templates and the detection idiom. Although we can use 'if constexpr' and variable templates in C++11 with appropriate uses of diagnostic pragmas, we can't have constexpr functions with multiple return statements. This means that in C++11 mode the _S_nothrow_construct and _S_nothrow_destroy helpers used for noexcept-specifiers still need to be overlaods using enable_if. Nearly everything else can be simplified to reduce overload resolution and enable_if checks. libstdc++-v3/ChangeLog: PR libstdc++/108619 * include/bits/alloc_traits.h (__allocator_traits_base): Add variable templates for detecting which allocator operations are supported. (allocator_traits): Use 'if constexpr' instead of dispatching to overloads constrained with enable_if. (allocator_traits>::construct): Use Construct if construct_at is not supported. Use __is_nothrow_new_constructible for noexcept-specifier. (allocator_traits>::construct): Use __is_nothrow_new_constructible for noexcept-specifier. * include/bits/new_allocator.h (construct): Likewise. * include/ext/malloc_allocator.h (construct): Likewise. * include/std/type_traits (__is_nothrow_new_constructible): New variable template. * testsuite/20_util/allocator/89510.cc: Adjust expected results. * testsuite/ext/malloc_allocator/89510.cc: Likewise. * testsuite/ext/new_allocator/89510.cc: Likewise. * testsuite/20_util/allocator_traits/members/108619.cc: New test. --- libstdc++-v3/include/bits/alloc_traits.h | 351 ++++++++++++------ libstdc++-v3/include/bits/new_allocator.h | 2 +- libstdc++-v3/include/ext/malloc_allocator.h | 2 +- libstdc++-v3/include/std/type_traits | 15 + .../testsuite/20_util/allocator/89510.cc | 14 +- .../allocator_traits/members/108619.cc | 35 ++ .../testsuite/ext/malloc_allocator/89510.cc | 14 +- .../testsuite/ext/new_allocator/89510.cc | 14 +- 8 files changed, 314 insertions(+), 133 deletions(-) create mode 100644 libstdc++-v3/testsuite/20_util/allocator_traits/members/108619.cc diff --git a/libstdc++-v3/include/bits/alloc_traits.h b/libstdc++-v3/include/bits/alloc_traits.h index 82fc79c7b9f..c2acc2ab207 100644 --- a/libstdc++-v3/include/bits/alloc_traits.h +++ b/libstdc++-v3/include/bits/alloc_traits.h @@ -48,10 +48,19 @@ namespace std _GLIBCXX_VISIBILITY(default) _GLIBCXX_BEGIN_NAMESPACE_VERSION #if __cplusplus >= 201103L + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wc++14-extensions" // for variable templates +#pragma GCC diagnostic ignored "-Wc++17-extensions" // for if-constexpr + /// @cond undocumented struct __allocator_traits_base { +#if __cpp_concepts + template +#else template +#endif struct __rebind : __replace_first_arg<_Tp, _Up> { static_assert(is_same< @@ -61,8 +70,13 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION }; template +#if __cpp_concepts + requires requires { typename _Tp::template rebind<_Up>::other; } + struct __rebind<_Tp, _Up> +#else struct __rebind<_Tp, _Up, __void_t::other>> +#endif { using type = typename _Tp::template rebind<_Up>::other; @@ -89,6 +103,135 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION using __pocs = typename _Tp::propagate_on_container_swap; template using __equal = __type_identity; + + // __has_allocate_hint is true if a.allocate(n, hint) is well-formed. +#if __cpp_concepts + template + static constexpr bool __has_allocate_hint + = requires (_Alloc& __a, _Sz __n, _Vp __hint) { + __a.allocate(__n, __hint); + }; +#else + template + using __allocate_hint_t + = decltype(std::declval<_Alloc&>() + .allocate(std::declval<_Sz>(), std::declval<_Vp>())); + template + static constexpr bool __has_allocate_hint = false; + template + static constexpr bool + __has_allocate_hint<_Alloc, _Sz, _Vp, + __void_t<__allocate_hint_t<_Alloc, _Sz, _Vp>>> + = true; +#endif + + // __has_construct is true if a.construct(p, args...) is well-formed. + // __can_construct is true if either __has_construct is true, or if + // a placement new-expression for T(args...) is well-formed. We use this + // to constrain allocator_traits::construct, as a libstdc++ extension. +#if __cpp_concepts + template + static constexpr bool __has_construct + = requires (_Alloc& __a, _Tp* __p, _Args&&... __args) { + __a.construct(__p, std::forward<_Args>(__args)...); + }; + template + static constexpr bool __can_construct_at + = requires (_Tp* __p, _Args&&... __args) { +#if __cpp_constexpr_dynamic_alloc + std::construct_at(__p, std::forward<_Args>(__args)...); +#else + ::new((void*)__p) _Tp(std::forward<_Args>(__args)...); +#endif + }; + template + static constexpr bool __can_construct + = __has_construct<_Alloc, _Tp, _Args...> + || __can_construct_at<_Tp, _Args...>; +#else + template + using __construct_t + = decltype(std::declval<_Alloc&>().construct(std::declval<_Tp*>(), + std::declval<_Args>()...)); + template + static constexpr bool __has_construct_impl = false; + template + static constexpr bool + __has_construct_impl<_Alloc, _Tp, + __void_t<__construct_t<_Alloc, _Tp, _Args...>>, + _Args...> + = true; + template + static constexpr bool __has_construct + = __has_construct_impl<_Alloc, _Tp, void, _Args...>; + template + using __new_expr_t + = decltype(::new((void*)0) _Tp(std::declval<_Args>()...)); + template + static constexpr bool __has_new_expr = false; + template + static constexpr bool + __has_new_expr<_Tp, __void_t<__new_expr_t<_Tp, _Args...>>, _Args...> + = true; + template + static constexpr bool __can_construct + = __has_construct<_Alloc, _Tp, _Args...> + || __has_new_expr<_Tp, void, _Args...>; +#endif + + // __has_destroy is true if a.destroy(p) is well-formed. +#if __cpp_concepts + template + static constexpr bool __has_destroy = requires (_Alloc& __a, _Tp* __p) { + __a.destroy(__p); + }; +#else + template + using __destroy_t + = decltype(std::declval<_Alloc&>().destroy(std::declval<_Tp*>())); + template + static constexpr bool __has_destroy = false; + template + static constexpr bool __has_destroy<_Alloc, _Tp, + __void_t<__destroy_t<_Alloc, _Tp>>> + = true; +#endif + + // __has_max_size is true if a.max_size() is well-formed. +#if __cpp_concepts + template + static constexpr bool __has_max_size = requires (const _Alloc& __a) { + __a.max_size(); + }; +#else + template + using __max_size_t = decltype(std::declval().max_size()); + template + static constexpr bool __has_max_size = false; + template + static constexpr bool __has_max_size<_Alloc, + __void_t<__max_size_t<_Alloc>>> + = true; +#endif + + // __has_soccc is true if a.select_on_container_copy_construction() + // is well-formed. +#if __cpp_concepts + template + static constexpr bool __has_soccc = requires (const _Alloc& __a) { + __a.select_on_container_copy_construction(); + }; +#else + template + using __soccc_t + = decltype(std::declval() + .select_on_container_copy_construction()); + template + static constexpr bool __has_soccc = false; + template + static constexpr bool __has_soccc<_Alloc, __void_t<__soccc_t<_Alloc>>> + = true; +#endif }; template @@ -230,98 +373,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION template using rebind_traits = allocator_traits>; - private: - template - static constexpr auto - _S_allocate(_Alloc2& __a, size_type __n, const_void_pointer __hint, int) - -> decltype(__a.allocate(__n, __hint)) - { return __a.allocate(__n, __hint); } - - template - static constexpr pointer - _S_allocate(_Alloc2& __a, size_type __n, const_void_pointer, ...) - { return __a.allocate(__n); } - - template - struct __construct_helper - { - template()->construct( - std::declval<_Tp*>(), std::declval<_Args>()...))> - static true_type __test(int); - - template - static false_type __test(...); - - using type = decltype(__test<_Alloc>(0)); - }; - - template - using __has_construct - = typename __construct_helper<_Tp, _Args...>::type; - - template - static _GLIBCXX14_CONSTEXPR _Require<__has_construct<_Tp, _Args...>> - _S_construct(_Alloc& __a, _Tp* __p, _Args&&... __args) - noexcept(noexcept(__a.construct(__p, std::forward<_Args>(__args)...))) - { __a.construct(__p, std::forward<_Args>(__args)...); } - - template - static _GLIBCXX14_CONSTEXPR - _Require<__and_<__not_<__has_construct<_Tp, _Args...>>, - is_constructible<_Tp, _Args...>>> - _S_construct(_Alloc&, _Tp* __p, _Args&&... __args) - noexcept(std::is_nothrow_constructible<_Tp, _Args...>::value) - { -#if __cplusplus <= 201703L - ::new((void*)__p) _Tp(std::forward<_Args>(__args)...); -#else - std::construct_at(__p, std::forward<_Args>(__args)...); -#endif - } - - template - static _GLIBCXX14_CONSTEXPR auto - _S_destroy(_Alloc2& __a, _Tp* __p, int) - noexcept(noexcept(__a.destroy(__p))) - -> decltype(__a.destroy(__p)) - { __a.destroy(__p); } - - template - static _GLIBCXX14_CONSTEXPR void - _S_destroy(_Alloc2&, _Tp* __p, ...) - noexcept(std::is_nothrow_destructible<_Tp>::value) - { std::_Destroy(__p); } - - template - static constexpr auto - _S_max_size(_Alloc2& __a, int) - -> decltype(__a.max_size()) - { return __a.max_size(); } - - template - static constexpr size_type - _S_max_size(_Alloc2&, ...) - { - // _GLIBCXX_RESOLVE_LIB_DEFECTS - // 2466. allocator_traits::max_size() default behavior is incorrect - return __gnu_cxx::__numeric_traits::__max - / sizeof(value_type); - } - - template - static constexpr auto - _S_select(_Alloc2& __a, int) - -> decltype(__a.select_on_container_copy_construction()) - { return __a.select_on_container_copy_construction(); } - - template - static constexpr _Alloc2 - _S_select(_Alloc2& __a, ...) - { return __a; } - - public: - /** * @brief Allocate memory. * @param __a An allocator. @@ -346,7 +397,12 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION */ _GLIBCXX_NODISCARD static _GLIBCXX20_CONSTEXPR pointer allocate(_Alloc& __a, size_type __n, const_void_pointer __hint) - { return _S_allocate(__a, __n, __hint, 0); } + { + if constexpr (__has_allocate_hint<_Alloc, size_type, const_void_pointer>) + return __a.allocate(__n, __hint); + else + return __a.allocate(__n); + } /** * @brief Deallocate memory. @@ -372,12 +428,20 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION * arguments @a __args... */ template - static _GLIBCXX20_CONSTEXPR auto +#if __cpp_concepts && __cpp_constexpr_dynamic_alloc + requires __can_construct<_Alloc, _Tp, _Args...> + static constexpr void +#else + static __enable_if_t<__can_construct<_Alloc, _Tp, _Args...>> +#endif construct(_Alloc& __a, _Tp* __p, _Args&&... __args) - noexcept(noexcept(_S_construct(__a, __p, - std::forward<_Args>(__args)...))) - -> decltype(_S_construct(__a, __p, std::forward<_Args>(__args)...)) - { _S_construct(__a, __p, std::forward<_Args>(__args)...); } + noexcept(_S_nothrow_construct<_Tp, _Args...>()) + { + if constexpr (__has_construct<_Alloc, _Tp, _Args...>) + __a.construct(__p, std::forward<_Args>(__args)...); + else + std::_Construct(__p, std::forward<_Args>(__args)...); + } /** * @brief Destroy an object of type @a _Tp @@ -390,8 +454,13 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION template static _GLIBCXX20_CONSTEXPR void destroy(_Alloc& __a, _Tp* __p) - noexcept(noexcept(_S_destroy(__a, __p, 0))) - { _S_destroy(__a, __p, 0); } + noexcept(_S_nothrow_destroy<_Tp>()) + { + if constexpr (__has_destroy<_Alloc, _Tp>) + __a.destroy(__p); + else + std::_Destroy(__p); + } /** * @brief The maximum supported allocation size @@ -403,7 +472,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION */ static _GLIBCXX20_CONSTEXPR size_type max_size(const _Alloc& __a) noexcept - { return _S_max_size(__a, 0); } + { + if constexpr (__has_max_size<_Alloc>) + return __a.max_size(); + else + // _GLIBCXX_RESOLVE_LIB_DEFECTS + // 2466. allocator_traits::max_size() default behavior is incorrect + return __gnu_cxx::__numeric_traits::__max + / sizeof(value_type); + } /** * @brief Obtain an allocator to use when copying a container. @@ -415,8 +492,61 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION */ static _GLIBCXX20_CONSTEXPR _Alloc select_on_container_copy_construction(const _Alloc& __rhs) - { return _S_select(__rhs, 0); } + { + if constexpr (__has_soccc<_Alloc>) + return __rhs.select_on_container_copy_construction(); + else + return __rhs; + } + + private: +#if __cpp_constexpr >= 201304 // >= C++14 + template + static constexpr bool + _S_nothrow_construct(_Alloc* __a = nullptr, _Tp* __p = nullptr) + { + if constexpr (__has_construct<_Alloc, _Tp, _Args...>) + return noexcept(__a->construct(__p, std::declval<_Args>()...)); + else + return __is_nothrow_new_constructible<_Tp, _Args...>; + } + + template + static constexpr bool + _S_nothrow_destroy(_Alloc* __a = nullptr, _Tp* __p = nullptr) + { + if constexpr (__has_destroy<_Alloc, _Tp>) + return noexcept(__a->destroy(__p)); + else + return is_nothrow_destructible<_Tp>::value; + } +#else + template + static constexpr + __enable_if_t<__has_construct<_Alloc, _Tp, _Args...>, bool> + _S_nothrow_construct(_Alloc* __a = nullptr, _Tp* __p = nullptr) + { return noexcept(__a->construct(__p, std::declval<_Args>()...)); } + + template + static constexpr + __enable_if_t, bool> + _S_nothrow_construct(_Alloc* = nullptr, _Tp* __p = nullptr) + { return __is_nothrow_new_constructible<_Tp, _Args...>; } + + template + static constexpr + __enable_if_t<__has_destroy<_Alloc, _Tp>, bool> + _S_nothrow_destroy(_Alloc* __a = nullptr, _Tp* __p = nullptr) + { return noexcept(__a->destroy(__p)); } + + template + static constexpr + __enable_if_t, bool> + _S_nothrow_destroy(_Alloc* = nullptr, _Tp* __p = nullptr) + { return is_nothrow_destructible<_Tp>::value; } +#endif }; +#pragma GCC diagnostic pop #if _GLIBCXX_HOSTED /// Partial specialization for std::allocator. @@ -526,14 +656,20 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION template [[__gnu__::__always_inline__]] static _GLIBCXX20_CONSTEXPR void - construct(allocator_type& __a __attribute__((__unused__)), _Up* __p, - _Args&&... __args) - noexcept(std::is_nothrow_constructible<_Up, _Args...>::value) + construct(allocator_type& __a __attribute__((__unused__)), + _Up* __p, _Args&&... __args) +#if __cplusplus <= 201703L + noexcept(noexcept(__a.construct(__p, std::forward<_Args>(__args)...))) +#else + noexcept(__is_nothrow_new_constructible<_Up, _Args...>) +#endif { #if __cplusplus <= 201703L __a.construct(__p, std::forward<_Args>(__args)...); -#else +#elif __cpp_constexpr_dynamic_alloc // >= C++20 std::construct_at(__p, std::forward<_Args>(__args)...); +#else + std::_Construct(__p, std::forward<_Args>(__args)...); #endif } @@ -653,7 +789,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION [[__gnu__::__always_inline__]] static _GLIBCXX20_CONSTEXPR void construct(allocator_type&, _Up* __p, _Args&&... __args) - noexcept(std::is_nothrow_constructible<_Up, _Args...>::value) + noexcept(__is_nothrow_new_constructible<_Up, _Args...>) { std::_Construct(__p, std::forward<_Args>(__args)...); } /** @@ -944,6 +1080,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION std::_Destroy(__first, __last); } #endif + /// @endcond _GLIBCXX_END_NAMESPACE_VERSION diff --git a/libstdc++-v3/include/bits/new_allocator.h b/libstdc++-v3/include/bits/new_allocator.h index 5dcdee11c4d..3a749dc91db 100644 --- a/libstdc++-v3/include/bits/new_allocator.h +++ b/libstdc++-v3/include/bits/new_allocator.h @@ -187,7 +187,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION __attribute__((__always_inline__)) void construct(_Up* __p, _Args&&... __args) - noexcept(std::is_nothrow_constructible<_Up, _Args...>::value) + noexcept(__is_nothrow_new_constructible<_Up, _Args...>) { ::new((void *)__p) _Up(std::forward<_Args>(__args)...); } template diff --git a/libstdc++-v3/include/ext/malloc_allocator.h b/libstdc++-v3/include/ext/malloc_allocator.h index 36513780925..2a58847b8a9 100644 --- a/libstdc++-v3/include/ext/malloc_allocator.h +++ b/libstdc++-v3/include/ext/malloc_allocator.h @@ -161,7 +161,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION template void construct(_Up* __p, _Args&&... __args) - noexcept(std::is_nothrow_constructible<_Up, _Args...>::value) + noexcept(std::__is_nothrow_new_constructible<_Up, _Args...>) { ::new((void *)__p) _Up(std::forward<_Args>(__args)...); } template diff --git a/libstdc++-v3/include/std/type_traits b/libstdc++-v3/include/std/type_traits index c39a3792537..7415e200e09 100644 --- a/libstdc++-v3/include/std/type_traits +++ b/libstdc++-v3/include/std/type_traits @@ -1643,6 +1643,21 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION #endif #endif // __cpp_lib_is_nothrow_convertible +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wc++14-extensions" // for variable templates + template + struct __is_nothrow_new_constructible_impl + : __bool_constant< + noexcept(::new(std::declval()) _Tp(std::declval<_Args>()...)) + > + { }; + + template + _GLIBCXX17_INLINE constexpr bool __is_nothrow_new_constructible + = __and_, + __is_nothrow_new_constructible_impl<_Tp, _Args...>>::value; +#pragma GCC diagnostic pop + // Const-volatile modifications. /// remove_const diff --git a/libstdc++-v3/testsuite/20_util/allocator/89510.cc b/libstdc++-v3/testsuite/20_util/allocator/89510.cc index 95c85d2634e..91526462a09 100644 --- a/libstdc++-v3/testsuite/20_util/allocator/89510.cc +++ b/libstdc++-v3/testsuite/20_util/allocator/89510.cc @@ -136,13 +136,11 @@ struct Z }; Z* zp; -// These construct calls should be noexcept, but they are false because -// they use is_nothrow_constructible which depends on is_nothrow_destructible. #if __cplusplus <= 201703L -static_assert( ! noexcept(a.construct(zp)), "wrong" ); -static_assert( ! noexcept(a.construct(zp, 1)), "wrong" ); -static_assert( ! noexcept(a.destroy(zp)), "" ); +static_assert( noexcept(a.construct(zp)), "" ); +static_assert( noexcept(a.construct(zp, 1)), "" ); +static_assert( ! noexcept(a.destroy(zp)), "~Z is noexcept(false)" ); #endif -static_assert( ! noexcept(AT::construct(a, zp)), "" ); -static_assert( ! noexcept(AT::construct(a, zp, 1)), "" ); -static_assert( ! noexcept(AT::destroy(a, zp)), "" ); +static_assert( noexcept(AT::construct(a, zp)), "" ); +static_assert( noexcept(AT::construct(a, zp, 1)), "" ); +static_assert( ! noexcept(AT::destroy(a, zp)), "~Z is noexcept(false)" ); diff --git a/libstdc++-v3/testsuite/20_util/allocator_traits/members/108619.cc b/libstdc++-v3/testsuite/20_util/allocator_traits/members/108619.cc new file mode 100644 index 00000000000..01bf611b804 --- /dev/null +++ b/libstdc++-v3/testsuite/20_util/allocator_traits/members/108619.cc @@ -0,0 +1,35 @@ +// { dg-do compile { target c++11 } } + +#include + +template +struct Alloc +{ + Alloc() = default; + + template Alloc(const Alloc&) { } + + using value_type = T; + + T* allocate(unsigned n) + { return std::allocator().allocate(n); } + + void deallocate(T* p, unsigned n) + { return std::allocator().deallocate(p, n); } + + template void destroy(U* p){ p->~U(); } +}; + + +class S +{ + ~S() = default; + + friend Alloc; +}; + +void +test_pr108619(Alloc a, S* p) +{ + std::allocator_traits>::construct(a, p); +} diff --git a/libstdc++-v3/testsuite/ext/malloc_allocator/89510.cc b/libstdc++-v3/testsuite/ext/malloc_allocator/89510.cc index 1889c88d6e5..771facbdf74 100644 --- a/libstdc++-v3/testsuite/ext/malloc_allocator/89510.cc +++ b/libstdc++-v3/testsuite/ext/malloc_allocator/89510.cc @@ -137,13 +137,11 @@ struct Z }; Z* zp; -// These construct calls should be noexcept, but they are false because -// they use is_nothrow_constructible which depends on is_nothrow_destructible. #if __cplusplus <= 201703L -static_assert( ! noexcept(a.construct(zp)), "wrong" ); -static_assert( ! noexcept(a.construct(zp, 1)), "wrong" ); -static_assert( ! noexcept(a.destroy(zp)), "" ); +static_assert( noexcept(a.construct(zp)), "" ); +static_assert( noexcept(a.construct(zp, 1)), "" ); +static_assert( ! noexcept(a.destroy(zp)), "~Z is noexcept(false)" ); #endif -static_assert( ! noexcept(AT::construct(a, zp)), "" ); -static_assert( ! noexcept(AT::construct(a, zp, 1)), "" ); -static_assert( ! noexcept(AT::destroy(a, zp)), "" ); +static_assert( noexcept(AT::construct(a, zp)), "" ); +static_assert( noexcept(AT::construct(a, zp, 1)), "" ); +static_assert( ! noexcept(AT::destroy(a, zp)), "~Z is noexcept(false)" ); diff --git a/libstdc++-v3/testsuite/ext/new_allocator/89510.cc b/libstdc++-v3/testsuite/ext/new_allocator/89510.cc index 06384ae5570..7fc443831c9 100644 --- a/libstdc++-v3/testsuite/ext/new_allocator/89510.cc +++ b/libstdc++-v3/testsuite/ext/new_allocator/89510.cc @@ -137,13 +137,11 @@ struct Z }; Z* zp; -// These construct calls should be noexcept, but they are false because -// they use is_nothrow_constructible which depends on is_nothrow_destructible. #if __cplusplus <= 201703L -static_assert( ! noexcept(a.construct(zp)), "wrong" ); -static_assert( ! noexcept(a.construct(zp, 1)), "wrong" ); -static_assert( ! noexcept(a.destroy(zp)), "" ); +static_assert( noexcept(a.construct(zp)), "" ); +static_assert( noexcept(a.construct(zp, 1)), "" ); +static_assert( ! noexcept(a.destroy(zp)), "~Z is noexcept(false)" ); #endif -static_assert( ! noexcept(AT::construct(a, zp)), "" ); -static_assert( ! noexcept(AT::construct(a, zp, 1)), "" ); -static_assert( ! noexcept(AT::destroy(a, zp)), "" ); +static_assert( noexcept(AT::construct(a, zp)), "" ); +static_assert( noexcept(AT::construct(a, zp, 1)), "" ); +static_assert( ! noexcept(AT::destroy(a, zp)), "~Z is noexcept(false)" );