From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by sourceware.org (Postfix) with ESMTPS id B4489385843F for ; Thu, 25 Nov 2021 23:12:47 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.1 sourceware.org B4489385843F Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id us-mta-152-ol1RQBleMyKUUZpa_GZdQw-1; Thu, 25 Nov 2021 18:12:46 -0500 X-MC-Unique: ol1RQBleMyKUUZpa_GZdQw-1 Received: from smtp.corp.redhat.com (int-mx07.intmail.prod.int.phx2.redhat.com [10.5.11.22]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id 13BF4180FD62; Thu, 25 Nov 2021 23:12:45 +0000 (UTC) Received: from localhost (unknown [10.33.36.17]) by smtp.corp.redhat.com (Postfix) with ESMTP id 944581002390; Thu, 25 Nov 2021 23:12:44 +0000 (UTC) From: Jonathan Wakely To: libstdc++@gcc.gnu.org, gcc-patches@gcc.gnu.org Subject: [committed] libstdc++: Make std::pointer_traits SFINAE-friendly [PR96416] Date: Thu, 25 Nov 2021 23:12:43 +0000 Message-Id: <20211125231243.1503399-1-jwakely@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.84 on 10.5.11.22 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Content-Transfer-Encoding: 8bit Content-Type: text/plain; charset="US-ASCII" X-Spam-Status: No, score=-14.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_LOW, RCVD_IN_MSPIKE_H2, SPF_HELO_NONE, SPF_NONE, TXREP autolearn=ham autolearn_force=no version=3.4.4 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on server2.sourceware.org X-BeenThere: libstdc++@gcc.gnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Libstdc++ mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Thu, 25 Nov 2021 23:12:50 -0000 Tested x86_64-linux, pushed to trunk. This implements the resolution I'm proposing for LWG 3545, to avoid hard errors when using std::to_address for types that make pointer_traits ill-formed. Consistent with std::iterator_traits, instantiating std::pointer_traits for a non-pointer type will be well-formed, but give an empty type with no member types. This avoids the problematic cases for std::to_address. Additionally, the pointer_to member is now only declared when the element type is not cv void (and for C++20, when the function body would be well-formed). The rebind member was already SFINAE-friendly in our implementation. libstdc++-v3/ChangeLog: PR libstdc++/96416 * include/bits/ptr_traits.h (pointer_traits): Reimplement to be SFINAE-friendly (LWG 3545). * testsuite/20_util/pointer_traits/lwg3545.cc: New test. * testsuite/20_util/to_address/1_neg.cc: Adjust dg-error line. * testsuite/20_util/to_address/lwg3545.cc: New test. --- libstdc++-v3/include/bits/ptr_traits.h | 167 +++++++++++++----- .../20_util/pointer_traits/lwg3545.cc | 120 +++++++++++++ .../testsuite/20_util/to_address/1_neg.cc | 2 +- .../testsuite/20_util/to_address/lwg3545.cc | 12 ++ 4 files changed, 251 insertions(+), 50 deletions(-) create mode 100644 libstdc++-v3/testsuite/20_util/pointer_traits/lwg3545.cc create mode 100644 libstdc++-v3/testsuite/20_util/to_address/lwg3545.cc diff --git a/libstdc++-v3/include/bits/ptr_traits.h b/libstdc++-v3/include/bits/ptr_traits.h index 115b86d43e4..4987fa9942f 100644 --- a/libstdc++-v3/include/bits/ptr_traits.h +++ b/libstdc++-v3/include/bits/ptr_traits.h @@ -35,6 +35,7 @@ #include #if __cplusplus > 201703L +#include #define __cpp_lib_constexpr_memory 201811L namespace __gnu_debug { struct _Safe_iterator_base; } #endif @@ -45,55 +46,119 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION class __undefined; - // Given Template return T, otherwise invalid. + // For a specialization `SomeTemplate` the member `type` is T, + // otherwise `type` is `__undefined`. template struct __get_first_arg { using type = __undefined; }; - template class _Template, typename _Tp, + template class _SomeTemplate, typename _Tp, typename... _Types> - struct __get_first_arg<_Template<_Tp, _Types...>> + struct __get_first_arg<_SomeTemplate<_Tp, _Types...>> { using type = _Tp; }; - template - using __get_first_arg_t = typename __get_first_arg<_Tp>::type; - - // Given Template and U return Template, otherwise invalid. + // For a specialization `SomeTemplate` and a type `U` the member + // `type` is `SomeTemplate`, otherwise there is no member `type`. template struct __replace_first_arg { }; - template class _Template, typename _Up, + template class _SomeTemplate, typename _Up, typename _Tp, typename... _Types> - struct __replace_first_arg<_Template<_Tp, _Types...>, _Up> - { using type = _Template<_Up, _Types...>; }; + struct __replace_first_arg<_SomeTemplate<_Tp, _Types...>, _Up> + { using type = _SomeTemplate<_Up, _Types...>; }; - template - using __replace_first_arg_t = typename __replace_first_arg<_Tp, _Up>::type; - - template - using __make_not_void - = __conditional_t::value, __undefined, _Tp>; - - /** - * @brief Uniform interface to all pointer-like types - * @ingroup pointer_abstractions - */ +#if __cpp_concepts + // When concepts are supported detection of _Ptr::element_type is done + // by a requires-clause, so __ptr_traits_elem_t only needs to do this: template - struct pointer_traits + using __ptr_traits_elem_t = typename __get_first_arg<_Ptr>::type; +#else + // Detect the element type of a pointer-like type. + template + struct __ptr_traits_elem : __get_first_arg<_Ptr> + { }; + + // Use _Ptr::element_type if is a valid type. + template + struct __ptr_traits_elem<_Ptr, __void_t> + { using type = typename _Ptr::element_type; }; + + template + using __ptr_traits_elem_t = typename __ptr_traits_elem<_Ptr>::type; +#endif + + // Define pointer_traits

::pointer_to. + template::value> + struct __ptr_traits_ptr_to + { + using pointer = _Ptr; + using element_type = _Elt; + + /** + * @brief Obtain a pointer to an object + * @param __r A reference to an object of type `element_type` + * @return `pointer::pointer_to(__e)` + * @pre `pointer::pointer_to(__e)` is a valid expression. + */ + static pointer + pointer_to(element_type& __e) +#if __cpp_lib_concepts + requires requires { + { pointer::pointer_to(__e) } -> convertible_to; + } +#endif + { return pointer::pointer_to(__e); } + }; + + // Do not define pointer_traits

::pointer_to if element type is void. + template + struct __ptr_traits_ptr_to<_Ptr, _Elt, true> + { }; + + // Partial specialization defining pointer_traits::pointer_to(T&). + template + struct __ptr_traits_ptr_to<_Tp*, _Tp, false> + { + using pointer = _Tp*; + using element_type = _Tp; + + /** + * @brief Obtain a pointer to an object + * @param __r A reference to an object of type `element_type` + * @return `addressof(__r)` + */ + static _GLIBCXX20_CONSTEXPR pointer + pointer_to(element_type& __r) noexcept + { return std::addressof(__r); } + }; + + template + struct __ptr_traits_impl : __ptr_traits_ptr_to<_Ptr, _Elt> { private: - template - using __element_type = typename _Tp::element_type; + template + struct __difference { using type = ptrdiff_t; }; template - using __difference_type = typename _Tp::difference_type; +#if __cpp_concepts + requires requires { typename _Tp::difference_type; } + struct __difference<_Tp> +#else + struct __difference<_Tp, __void_t> +#endif + { using type = typename _Tp::difference_type; }; template struct __rebind : __replace_first_arg<_Tp, _Up> { }; template +#if __cpp_concepts + requires requires { typename _Tp::template rebind<_Up>; } + struct __rebind<_Tp, _Up> +#else struct __rebind<_Tp, _Up, __void_t>> +#endif { using type = typename _Tp::template rebind<_Up>; }; public: @@ -101,31 +166,45 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION using pointer = _Ptr; /// The type pointed to. - using element_type - = __detected_or_t<__get_first_arg_t<_Ptr>, __element_type, _Ptr>; + using element_type = _Elt; /// The type used to represent the difference between two pointers. - using difference_type - = __detected_or_t; + using difference_type = typename __difference<_Ptr>::type; /// A pointer to a different type. template using rebind = typename __rebind<_Ptr, _Up>::type; - - static _Ptr - pointer_to(__make_not_void& __e) - { return _Ptr::pointer_to(__e); } - - static_assert(!is_same::value, - "pointer type defines element_type or is like SomePointer"); }; + // _GLIBCXX_RESOLVE_LIB_DEFECTS + // 3545. std::pointer_traits should be SFINAE-friendly + template + struct __ptr_traits_impl<_Ptr, __undefined> + { }; + + /** + * @brief Uniform interface to all pointer-like types + * @ingroup pointer_abstractions + * @since C++11 + */ + template + struct pointer_traits : __ptr_traits_impl<_Ptr, __ptr_traits_elem_t<_Ptr>> + { }; + +#if __cpp_concepts + template requires requires { typename _Ptr::element_type; } + struct pointer_traits<_Ptr> + : __ptr_traits_impl<_Ptr, typename _Ptr::element_type> + { }; +#endif + /** * @brief Partial specialization for built-in pointers. * @ingroup pointer_abstractions + * @since C++11 */ template - struct pointer_traits<_Tp*> + struct pointer_traits<_Tp*> : __ptr_traits_ptr_to<_Tp*, _Tp> { /// The pointer type typedef _Tp* pointer; @@ -133,18 +212,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION typedef _Tp element_type; /// Type used to represent the difference between two pointers typedef ptrdiff_t difference_type; - - template - using rebind = _Up*; - - /** - * @brief Obtain a pointer to an object - * @param __r A reference to an object of type @c element_type - * @return @c addressof(__r) - */ - static _GLIBCXX20_CONSTEXPR pointer - pointer_to(__make_not_void& __r) noexcept - { return std::addressof(__r); } + /// A pointer to a different type. + template using rebind = _Up*; }; /// Convenience alias for rebinding pointers. diff --git a/libstdc++-v3/testsuite/20_util/pointer_traits/lwg3545.cc b/libstdc++-v3/testsuite/20_util/pointer_traits/lwg3545.cc new file mode 100644 index 00000000000..8325cb66d08 --- /dev/null +++ b/libstdc++-v3/testsuite/20_util/pointer_traits/lwg3545.cc @@ -0,0 +1,120 @@ +// { dg-do compile { target c++11 } } + +// LWG 3545. std::pointer_traits should be SFINAE-friendly + +#include + +using std::is_same; + +template using void_t = void; + +template class Probe, typename T, typename = void> + struct has_member + : std::false_type { }; + +template class Probe, typename T> + struct has_member>> + : std::true_type { }; + +template + using element_type = typename T::element_type; +template + using pointer = typename T::pointer; +template + using difference_type = typename T::difference_type; +template + using rebind = typename T::template rebind; +template + using pointer_to = decltype(T::pointer_to(std::declval&>())); + +using invalid = std::pointer_traits; +invalid i; // invalid instantiation is not ill-formed + +static_assert( !has_member::value, "" ); +static_assert( !has_member::value, "" ); +static_assert( !has_member::value, "" ); +static_assert( !has_member::value, "" ); +static_assert( !has_member::value, "" ); + +struct I +{ + // These members should not be used by pointer_traits

::pointer. + using pointer = int; + using difference_type = int; + template using rebind = int; +}; + +using invalid2 = std::pointer_traits; + +static_assert( !has_member::value, "" ); +static_assert( !has_member::value, "" ); +static_assert( !has_member::value, "" ); +static_assert( !has_member::value, "" ); +static_assert( !has_member::value, "" ); + +struct P +{ + using element_type = long; + struct pointer { }; // should not be used by pointer_traits

::pointer + + P pointer_to(long&) const; // not static, should not be used. +}; +using Ptraits = std::pointer_traits

; +Ptraits p; + +static_assert( is_same, long>::value, "" ); +static_assert( is_same, P>::value, "" ); +static_assert( is_same, std::ptrdiff_t>::value, "" ); +static_assert( !has_member::value, "" ); +#if __cplusplus >= 202002L +// pointer_traits

::pointer_to(long&) is constrained in C++20 and later. +static_assert( !has_member::value, "" ); +#else +static_assert( is_same, P>::value, "" ); +#endif + +struct V { using element_type = const void; }; +using Vtraits = std::pointer_traits; +Vtraits v; + +static_assert( is_same, const void>::value, "" ); +static_assert( is_same, V>::value, "" ); +static_assert( is_same, std::ptrdiff_t>::value, "" ); +static_assert( !has_member::value, "" ); +static_assert( !has_member::value, "" ); + +template +struct clever_ptr +{ + static T obj; + + static clever_ptr pointer_to(T&) { return {}; } + constexpr T* operator->() const { return &obj; } +}; + +using Ctraits = std::pointer_traits>; + +static_assert( is_same, char>::value, "" ); +static_assert( is_same, clever_ptr>::value, "" ); +static_assert( is_same, std::ptrdiff_t>::value, "" ); +static_assert( is_same, clever_ptr>::value, "" ); +static_assert( is_same, clever_ptr>::value, "" ); + +#if __cplusplus >= 202002L +static_assert( std::to_address(clever_ptr{}) == &clever_ptr::obj, "" ); + +int the_int; + +template<> +struct std::pointer_traits> +{ + using element_type = int; + using difference_type = std::ptrdiff_t; + using pointer = clever_ptr; + + static constexpr int* to_address(pointer p) { return &the_int; } +}; + +// Should use pointer_traits>::to_address +static_assert( std::to_address(clever_ptr{}) == &the_int, "" ); +#endif diff --git a/libstdc++-v3/testsuite/20_util/to_address/1_neg.cc b/libstdc++-v3/testsuite/20_util/to_address/1_neg.cc index 5030f89b345..65aff7a8a5e 100644 --- a/libstdc++-v3/testsuite/20_util/to_address/1_neg.cc +++ b/libstdc++-v3/testsuite/20_util/to_address/1_neg.cc @@ -17,7 +17,7 @@ // { dg-options "-std=gnu++2a" } // { dg-do compile { target c++2a } } -// { dg-error "not a function pointer" "" { target *-*-* } 158 } +// { dg-error "not a function pointer" "" { target *-*-* } 0 } #include diff --git a/libstdc++-v3/testsuite/20_util/to_address/lwg3545.cc b/libstdc++-v3/testsuite/20_util/to_address/lwg3545.cc new file mode 100644 index 00000000000..4dc5fdcdde0 --- /dev/null +++ b/libstdc++-v3/testsuite/20_util/to_address/lwg3545.cc @@ -0,0 +1,12 @@ +// { dg-do compile { target c++20 } } +// { dg-options "-std=gnu++20" } + +#include + +template struct nttp_ptr +{ + T* operator->() const { return nullptr; } +}; + +// This gives an error in C++20, which the LWG 3545 resolution should fix: +auto x = std::to_address( nttp_ptr() ); -- 2.31.1