mirror of
https://github.com/gcc-mirror/gcc.git
synced 2024-11-21 13:40:47 +00:00
libstdc++: Refactor std::hash specializations
This attempts to simplify and clean up our std::hash code. The primary benefit is improved diagnostics for users when they do something wrong involving std::hash or unordered containers. An additional benefit is that for the unstable ABI (--enable-symvers=gnu-versioned-namespace) we can reduce the memory footprint of several std::hash specializations. In the current design, __hash_enum is a base class of the std::hash primary template, but the partial specialization of __hash_enum for non-enum types is disabled. This means that if a user forgets to specialize std::hash for their class type (or forgets to use a custom hash function for unordered containers) they get error messages about std::__hash_enum not being constructible. This is confusing when there is no enum type involved: why should users care about __hash_enum not being constructible if they're not trying to hash enums? This change makes the std::hash primary template only derive from __hash_enum when the template argument type is an enum. Otherwise, it derives directly from a new class template, __hash_not_enabled. This new class template defines the deleted members that cause a given std::hash specialization to be a disabled specialization (as per P0513R0). Now when users try to use a disabled specialization, they get more descriptive errors that mention __hash_not_enabled instead of __hash_enum. Additionally, adjust __hash_base to remove the deprecated result_type and argument_type typedefs for C++20 and later. In the current code we use a __poison_hash base class in the std::hash specializations for std::unique_ptr, std::optional, and std::variant. The primary template of __poison_hash has deleted special members, which is used to conditionally disable the derived std::hash specialization. This can also result in confusing diagnostics, because seeing "poison" in an enabled specialization is misleading. Only some uses of __poison_hash actually "poison" anything, i.e. cause a specialization to be disabled. In other cases it's just an empty base class that does nothing. This change removes __poison_hash and changes the std::hash specializations that were using it to conditionally derive from __hash_not_enabled instead. When the std::hash specialization is enabled, there is no more __poison_hash base class. However, to preserve the ABI properties of those std::hash specializations, we need to replace __poison_hash with some other empty base class. This is needed because in the current code std::hash<std::variant<int, const int>> has two __poison_hash<int> base classes, which must have unique addresses, so sizeof(std::hash<std::variant<int, const int>>) == 2. To preserve this unfortunate property, a new __hash_empty_base class is used as a base class to re-introduce du0plicate base classes that increase the class size. For the unstable ABI we don't use __hash_empty_base so the std::hash<std::variant<T...>> specializations are always size 1, and the class hierarchy is much simpler so will compile faster. Additionally, remove the result_type and argument_type typedefs from all disabled specializations of std::hash for std::unique_ptr, std::optional, and std::variant. Those typedefs are useless for disabled specializations, and although the standard doesn't say they must *not* be present for disabled specializations, it certainly only requires them for enabled specializations. Finally, for C++20 the typedefs are also removed from enabled specializations of std::hash for std::unique_ptr, std::optional, and std::variant. libstdc++-v3/ChangeLog: * doc/xml/manual/evolution.xml: Document removal of nested types from std::hash specializations. * doc/html/manual/api.html: Regenerate. * include/bits/functional_hash.h (__hash_base): Remove deprecated nested types for C++20. (__hash_empty_base): Define new class template. (__is_hash_enabled_for): Define new variable template. (__poison_hash): Remove. (__hash_not_enabled): Define new class template. (__hash_enum): Remove partial specialization for non-enums. (hash): Derive from __hash_not_enabled for non-enums, instead of __hash_enum. * include/bits/unique_ptr.h (__uniq_ptr_hash): Derive from __hash_base. Conditionally derive from __hash_empty_base. (__uniq_ptr_hash<>): Remove disabled specialization. (hash): Do not derive from __hash_base unconditionally. Conditionally derive from either __uniq_ptr_hash or __hash_not_enabled. * include/std/optional (__optional_hash_call_base): Remove. (__optional_hash): Define new class template. (hash): Derive from either (hash): Conditionally derive from either __optional_hash or __hash_not_enabled. Remove nested typedefs. * include/std/variant (_Base_dedup): Replace __poison_hash with __hash_empty_base. (__variant_hash_call_base_impl): Remove. (__variant_hash): Define new class template. (hash): Conditionally derive from either __variant_hash or __hash_not_enabled. Remove nested typedefs. * testsuite/20_util/optional/hash.cc: Check whether nested types are present. * testsuite/20_util/variant/hash.cc: Likewise. * testsuite/20_util/optional/hash_abi.cc: New test. * testsuite/20_util/unique_ptr/hash/abi.cc: New test. * testsuite/20_util/unique_ptr/hash/types.cc: New test. * testsuite/20_util/variant/hash_abi.cc: New test.
This commit is contained in:
parent
84e39b0750
commit
01ba02caa9
@ -506,4 +506,7 @@ and removed in C++20:
|
||||
<code class="filename"><cstdalign></code>,
|
||||
<code class="filename"><cstdbool></code>, and
|
||||
<code class="filename"><ctgmath></code>.
|
||||
</p><p>
|
||||
Nested <code class="code">result_type</code> and <code class="code">argument_type</code> removed from
|
||||
<code class="classname">std::hash</code> specializations for C++20.
|
||||
</p></div></div><div class="navfooter"><hr /><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="abi.html">Prev</a> </td><td width="20%" align="center"><a accesskey="u" href="appendix_porting.html">Up</a></td><td width="40%" align="right"> <a accesskey="n" href="backwards.html">Next</a></td></tr><tr><td width="40%" align="left" valign="top">ABI Policy and Guidelines </td><td width="20%" align="center"><a accesskey="h" href="../index.html">Home</a></td><td width="40%" align="right" valign="top"> Backwards Compatibility</td></tr></table></div></body></html>
|
@ -1145,6 +1145,11 @@ and removed in C++20:
|
||||
<filename class="headerfile"><ctgmath></filename>.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Nested <code>result_type</code> and <code>argument_type</code> removed from
|
||||
<classname>std::hash</classname> specializations for C++20.
|
||||
</para>
|
||||
|
||||
</section>
|
||||
|
||||
</section>
|
||||
|
@ -52,43 +52,44 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
|
||||
template<typename _Result, typename _Arg>
|
||||
struct __hash_base
|
||||
{
|
||||
#if __cplusplus < 202002L
|
||||
typedef _Result result_type _GLIBCXX17_DEPRECATED;
|
||||
typedef _Arg argument_type _GLIBCXX17_DEPRECATED;
|
||||
#endif
|
||||
};
|
||||
|
||||
#if ! _GLIBCXX_INLINE_VERSION
|
||||
// Some std::hash specializations inherit this for ABI compatibility reasons.
|
||||
template<typename _Tp> struct __hash_empty_base { };
|
||||
#endif
|
||||
|
||||
/// Primary class template hash.
|
||||
template<typename _Tp>
|
||||
struct hash;
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wc++14-extensions"
|
||||
template<typename _Tp, typename = void>
|
||||
struct __poison_hash
|
||||
{
|
||||
static constexpr bool __enable_hash_call = false;
|
||||
private:
|
||||
// Private rather than deleted to be non-trivially-copyable.
|
||||
__poison_hash(__poison_hash&&);
|
||||
~__poison_hash();
|
||||
};
|
||||
constexpr bool __is_hash_enabled_for = false;
|
||||
|
||||
template<typename _Tp>
|
||||
struct __poison_hash<_Tp, __void_t<decltype(hash<_Tp>()(declval<_Tp>()))>>
|
||||
{
|
||||
static constexpr bool __enable_hash_call = true;
|
||||
};
|
||||
constexpr bool
|
||||
__is_hash_enabled_for<_Tp,
|
||||
__void_t<decltype(hash<_Tp>()(declval<_Tp>()))>>
|
||||
= true;
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
// Helper struct for SFINAE-poisoning non-enum types.
|
||||
template<typename _Tp, bool = is_enum<_Tp>::value>
|
||||
struct __hash_enum
|
||||
// Helper struct for defining disabled specializations of std::hash.
|
||||
template<typename _Tp>
|
||||
struct __hash_not_enabled
|
||||
{
|
||||
private:
|
||||
// Private rather than deleted to be non-trivially-copyable.
|
||||
__hash_enum(__hash_enum&&);
|
||||
~__hash_enum();
|
||||
__hash_not_enabled(__hash_not_enabled&&) = delete;
|
||||
~__hash_not_enabled() = delete;
|
||||
};
|
||||
|
||||
// Helper struct for hash with enum types.
|
||||
template<typename _Tp>
|
||||
struct __hash_enum<_Tp, true> : public __hash_base<size_t, _Tp>
|
||||
template<typename _Tp, bool = true>
|
||||
struct __hash_enum : public __hash_base<size_t, _Tp>
|
||||
{
|
||||
size_t
|
||||
operator()(_Tp __val) const noexcept
|
||||
@ -99,9 +100,9 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
|
||||
};
|
||||
|
||||
/// Primary class template hash, usable for enum types only.
|
||||
// Use with non-enum types still SFINAES.
|
||||
template<typename _Tp>
|
||||
struct hash : __hash_enum<_Tp>
|
||||
struct hash
|
||||
: __conditional_t<__is_enum(_Tp), __hash_enum<_Tp>, __hash_not_enabled<_Tp>>
|
||||
{ };
|
||||
|
||||
/// Partial specializations for pointer types.
|
||||
|
@ -1012,11 +1012,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
|
||||
/// @} relates unique_ptr
|
||||
|
||||
/// @cond undocumented
|
||||
template<typename _Up, typename _Ptr = typename _Up::pointer,
|
||||
bool = __poison_hash<_Ptr>::__enable_hash_call>
|
||||
template<typename _Up, typename _Ptr = typename _Up::pointer>
|
||||
struct __uniq_ptr_hash
|
||||
: public __hash_base<size_t, _Up>
|
||||
#if ! _GLIBCXX_INLINE_VERSION
|
||||
: private __poison_hash<_Ptr>
|
||||
, private __hash_empty_base<_Ptr>
|
||||
#endif
|
||||
{
|
||||
size_t
|
||||
@ -1025,17 +1025,17 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
|
||||
{ return hash<_Ptr>()(__u.get()); }
|
||||
};
|
||||
|
||||
template<typename _Up, typename _Ptr>
|
||||
struct __uniq_ptr_hash<_Up, _Ptr, false>
|
||||
: private __poison_hash<_Ptr>
|
||||
{ };
|
||||
template<typename _Up>
|
||||
using __uniq_ptr_hash_base
|
||||
= __conditional_t<__is_hash_enabled_for<typename _Up::pointer>,
|
||||
__uniq_ptr_hash<_Up>,
|
||||
__hash_not_enabled<typename _Up::pointer>>;
|
||||
/// @endcond
|
||||
|
||||
/// std::hash specialization for unique_ptr.
|
||||
template<typename _Tp, typename _Dp>
|
||||
struct hash<unique_ptr<_Tp, _Dp>>
|
||||
: public __hash_base<size_t, unique_ptr<_Tp, _Dp>>,
|
||||
public __uniq_ptr_hash<unique_ptr<_Tp, _Dp>>
|
||||
: public __uniq_ptr_hash_base<unique_ptr<_Tp, _Dp>>
|
||||
{ };
|
||||
|
||||
#ifdef __glibcxx_make_unique // C++ >= 14 && HOSTED
|
||||
|
@ -1732,10 +1732,17 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
|
||||
|
||||
// Hash.
|
||||
|
||||
template<typename _Tp, typename _Up = remove_const_t<_Tp>,
|
||||
bool = __poison_hash<_Up>::__enable_hash_call>
|
||||
struct __optional_hash_call_base
|
||||
template<typename _Tp, typename _Up = remove_const_t<_Tp>>
|
||||
struct __optional_hash
|
||||
#if ! _GLIBCXX_INLINE_VERSION
|
||||
: public __hash_empty_base<_Up>
|
||||
#endif
|
||||
{
|
||||
#if __cplusplus < 202002L
|
||||
using result_type [[__deprecated__]] = size_t;
|
||||
using argument_type [[__deprecated__]] = optional<_Tp>;
|
||||
#endif
|
||||
|
||||
size_t
|
||||
operator()(const optional<_Tp>& __t) const
|
||||
noexcept(noexcept(hash<_Up>{}(*__t)))
|
||||
@ -1747,17 +1754,12 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
|
||||
}
|
||||
};
|
||||
|
||||
template<typename _Tp, typename _Up>
|
||||
struct __optional_hash_call_base<_Tp, _Up, false> {};
|
||||
|
||||
template<typename _Tp>
|
||||
struct hash<optional<_Tp>>
|
||||
: private __poison_hash<remove_const_t<_Tp>>,
|
||||
public __optional_hash_call_base<_Tp>
|
||||
{
|
||||
using result_type [[__deprecated__]] = size_t;
|
||||
using argument_type [[__deprecated__]] = optional<_Tp>;
|
||||
};
|
||||
: public __conditional_t<__is_hash_enabled_for<remove_const_t<_Tp>>,
|
||||
__optional_hash<_Tp>,
|
||||
__hash_not_enabled<_Tp>>
|
||||
{ };
|
||||
|
||||
template<typename _Tp>
|
||||
struct __is_fast_hash<hash<optional<_Tp>>> : __is_fast_hash<hash<_Tp>>
|
||||
|
@ -1094,7 +1094,8 @@ namespace __variant
|
||||
= __gen_vtable_impl<_Array_type, std::index_sequence<>>::_S_apply();
|
||||
};
|
||||
|
||||
template<size_t _Np, typename _Tp>
|
||||
#if ! _GLIBCXX_INLINE_VERSION
|
||||
template<size_t _Nm, typename _Tp>
|
||||
struct _Base_dedup : public _Tp { };
|
||||
|
||||
template<typename _Variant, typename __indices>
|
||||
@ -1103,7 +1104,9 @@ namespace __variant
|
||||
template<typename... _Types, size_t... __indices>
|
||||
struct _Variant_hash_base<variant<_Types...>,
|
||||
std::index_sequence<__indices...>>
|
||||
: _Base_dedup<__indices, __poison_hash<remove_const_t<_Types>>>... { };
|
||||
: _Base_dedup<__indices, __hash_empty_base<remove_const_t<_Types>>>...
|
||||
{ };
|
||||
#endif
|
||||
|
||||
// Equivalent to decltype(get<_Np>(as-variant(declval<_Variant>())))
|
||||
template<size_t _Np, typename _Variant,
|
||||
@ -1987,9 +1990,14 @@ namespace __detail::__variant
|
||||
#endif
|
||||
|
||||
/// @cond undocumented
|
||||
template<bool, typename... _Types>
|
||||
struct __variant_hash_call_base_impl
|
||||
template<typename... _Types>
|
||||
struct __variant_hash
|
||||
{
|
||||
#if __cplusplus < 202002L
|
||||
using result_type [[__deprecated__]] = size_t;
|
||||
using argument_type [[__deprecated__]] = variant<_Types...>;
|
||||
#endif
|
||||
|
||||
size_t
|
||||
operator()(const variant<_Types...>& __t) const
|
||||
noexcept((is_nothrow_invocable_v<hash<decay_t<_Types>>, _Types> && ...))
|
||||
@ -2009,31 +2017,26 @@ namespace __detail::__variant
|
||||
return __ret;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... _Types>
|
||||
struct __variant_hash_call_base_impl<false, _Types...> {};
|
||||
|
||||
template<typename... _Types>
|
||||
using __variant_hash_call_base =
|
||||
__variant_hash_call_base_impl<(__poison_hash<remove_const_t<_Types>>::
|
||||
__enable_hash_call &&...), _Types...>;
|
||||
/// @endcond
|
||||
|
||||
template<typename... _Types>
|
||||
struct hash<variant<_Types...>>
|
||||
: private __detail::__variant::_Variant_hash_base<
|
||||
variant<_Types...>, std::index_sequence_for<_Types...>>,
|
||||
public __variant_hash_call_base<_Types...>
|
||||
{
|
||||
using result_type [[__deprecated__]] = size_t;
|
||||
using argument_type [[__deprecated__]] = variant<_Types...>;
|
||||
};
|
||||
: __conditional_t<(__is_hash_enabled_for<remove_const_t<_Types>> && ...),
|
||||
__variant_hash<_Types...>,
|
||||
__hash_not_enabled<variant<_Types...>>>
|
||||
#if ! _GLIBCXX_INLINE_VERSION
|
||||
, __detail::__variant::_Variant_hash_base<variant<_Types...>,
|
||||
index_sequence_for<_Types...>>
|
||||
#endif
|
||||
{ };
|
||||
|
||||
template<>
|
||||
struct hash<monostate>
|
||||
{
|
||||
#if __cplusplus < 202002L
|
||||
using result_type [[__deprecated__]] = size_t;
|
||||
using argument_type [[__deprecated__]] = monostate;
|
||||
#endif
|
||||
|
||||
size_t
|
||||
operator()(const monostate&) const noexcept
|
||||
|
@ -49,3 +49,36 @@ int main()
|
||||
std::optional<const int> x3 = x2;
|
||||
VERIFY(std::hash<int>()(x) == std::hash<std::optional<const int>>()(x3));
|
||||
}
|
||||
|
||||
// Check for presence/absence of nested types.
|
||||
|
||||
template<typename T> using res_type = typename std::hash<T>::result_type;
|
||||
template<typename T> using arg_type = typename std::hash<T>::argument_type;
|
||||
|
||||
template<typename Opt, typename = void>
|
||||
constexpr bool has_res_type = false;
|
||||
template<typename Opt>
|
||||
constexpr bool has_res_type<Opt, std::void_t<res_type<Opt>>> = true;
|
||||
template<typename Opt, typename = void>
|
||||
constexpr bool has_arg_type = false;
|
||||
template<typename Opt>
|
||||
constexpr bool has_arg_type<Opt, std::void_t<arg_type<Opt>>> = true;
|
||||
|
||||
template<typename T>
|
||||
constexpr bool has_no_types
|
||||
= ! has_res_type<std::optional<T>> && ! has_arg_type<std::optional<T>>;
|
||||
|
||||
#if __cplusplus >= 202002L
|
||||
// Nested types result_type and argument_type are not present in C++20
|
||||
static_assert( has_no_types<int> );
|
||||
static_assert( has_no_types<double> );
|
||||
#else
|
||||
// Nested types result_type and argument_type are deprecated in C++17.
|
||||
using R1 = std::hash<std::optional<int>>::result_type; // { dg-warning "deprecated" "" { target c++17_only } }
|
||||
using A1 = std::hash<std::optional<int>>::argument_type; // { dg-warning "deprecated" "" { target c++17_only } }
|
||||
using R2 = std::hash<std::optional<char>>::result_type; // { dg-warning "deprecated" "" { target c++17_only } }
|
||||
using A2 = std::hash<std::optional<char>>::argument_type; // { dg-warning "deprecated" "" { target c++17_only } }
|
||||
#endif
|
||||
|
||||
// Disabled specializations do not have the nested types.
|
||||
static_assert( has_no_types<S> );
|
||||
|
35
libstdc++-v3/testsuite/20_util/optional/hash_abi.cc
Normal file
35
libstdc++-v3/testsuite/20_util/optional/hash_abi.cc
Normal file
@ -0,0 +1,35 @@
|
||||
// { dg-do compile { target c++17 } }
|
||||
|
||||
#include <optional>
|
||||
|
||||
struct S { }; // std::hash<S> is a disabled specialization.
|
||||
|
||||
template<typename T>
|
||||
constexpr std::size_t hash_size = sizeof(std::hash<std::optional<T>>);
|
||||
|
||||
template<typename... Ts>
|
||||
struct MultiHash : std::hash<std::optional<Ts>>...
|
||||
{ };
|
||||
|
||||
#if _GLIBCXX_INLINE_VERSION
|
||||
// For the unstable ABI the size should always be one.
|
||||
template<std::size_t Size, typename... Ts>
|
||||
constexpr bool check_hash_size = sizeof(MultiHash<Ts...>) == 1;
|
||||
#else
|
||||
// For the default ABI, each std::hash<std::optional<T>> specialization has
|
||||
// a base class of type __hash_empty_base<remove_cv_t<T>> and if
|
||||
// the same type occurs more than once they must have unique addresses.
|
||||
template<std::size_t Size, typename... Ts>
|
||||
constexpr bool check_hash_size = sizeof(MultiHash<Ts...>) == Size;
|
||||
#endif
|
||||
|
||||
static_assert( check_hash_size<1, int> );
|
||||
static_assert( check_hash_size<1, int, long, double> );
|
||||
static_assert( check_hash_size<2, int, const int> );
|
||||
static_assert( check_hash_size<2, int, long, const int> );
|
||||
|
||||
static_assert( check_hash_size<1, S> );
|
||||
static_assert( check_hash_size<1, int, S> );
|
||||
static_assert( check_hash_size<2, int, S, const int> );
|
||||
static_assert( check_hash_size<2, int, S, const int, const S> );
|
||||
static_assert( check_hash_size<2, int, S, const S, const int> );
|
64
libstdc++-v3/testsuite/20_util/unique_ptr/hash/abi.cc
Normal file
64
libstdc++-v3/testsuite/20_util/unique_ptr/hash/abi.cc
Normal file
@ -0,0 +1,64 @@
|
||||
// { dg-do compile { target c++17 } }
|
||||
|
||||
#include <memory>
|
||||
|
||||
struct S { }; // std::hash<S> is a disabled specialization.
|
||||
|
||||
template<typename T, typename D = std::default_delete<T>>
|
||||
constexpr std::size_t hash_size = sizeof(std::hash<std::unique_ptr<T, D>>);
|
||||
|
||||
template<typename... Ts>
|
||||
struct MultiHash : std::hash<std::unique_ptr<Ts>>...
|
||||
{ };
|
||||
|
||||
#if _GLIBCXX_INLINE_VERSION
|
||||
// For the unstable ABI the size should always be one.
|
||||
template<std::size_t Size, typename... Ts>
|
||||
constexpr bool check_hash_size = sizeof(MultiHash<Ts...>) == 1;
|
||||
#else
|
||||
// For the default ABI, each std::hash<std::unique_ptr<T,D >> specialization
|
||||
// has a base class of type __hash_empty_base<D::pointer> and if
|
||||
// the same type occurs more than once they must have unique addresses.
|
||||
template<std::size_t Size, typename... Ts>
|
||||
constexpr bool check_hash_size = sizeof(MultiHash<Ts...>) == Size;
|
||||
#endif
|
||||
|
||||
// All these types have distinct D::pointer types, so no duplicate base classes
|
||||
static_assert( check_hash_size<1, int>, "" );
|
||||
static_assert( check_hash_size<1, int, long, double>, "" );
|
||||
static_assert( check_hash_size<1, int, const int>, "" );
|
||||
static_assert( check_hash_size<1, int, long, const int>, "" );
|
||||
// Likewise for these disabled specializations:
|
||||
static_assert( check_hash_size<1, S>, "" );
|
||||
static_assert( check_hash_size<1, int, S>, "" );
|
||||
static_assert( check_hash_size<1, int, S, const int>, "" );
|
||||
static_assert( check_hash_size<1, int, S, const int, const S>, "" );
|
||||
static_assert( check_hash_size<1, int, S, const S, const int>, "" );
|
||||
|
||||
// But this has two base classes of type __hash_empty_base<int*>:
|
||||
static_assert( check_hash_size<2, int, int[]>, "" );
|
||||
static_assert( check_hash_size<2, int, int[], const int>, "" );
|
||||
// And this has two base classes of type __hash_not_enabled<S*>:
|
||||
static_assert( check_hash_size<2, S, S[]>, "" );
|
||||
static_assert( check_hash_size<2, S, S[], const S>, "" );
|
||||
|
||||
struct Del : std::default_delete<int> { };
|
||||
using P = std::unique_ptr<int>;
|
||||
using PD = std::unique_ptr<int, Del>;
|
||||
using PC = std::unique_ptr<int, std::default_delete<const int>>;
|
||||
using PA = std::unique_ptr<int[]>;
|
||||
struct HashClash
|
||||
: std::hash<P>, std::hash<PD>, std::hash<PC>, std::hash<PA>
|
||||
{ };
|
||||
#if _GLIBCXX_INLINE_VERSION
|
||||
static_assert(sizeof(HashClash) == 1, "No duplicate bases for unstable ABI");
|
||||
#else
|
||||
static_assert(sizeof(HashClash) == 4, "four __hash_empty_base<int*> bases");
|
||||
#endif
|
||||
|
||||
struct Del2 : std::default_delete<const int> { using pointer = const int*; };
|
||||
using PD2 = std::unique_ptr<int, Del2>;
|
||||
struct Hash2
|
||||
: std::hash<PD>, std::hash<PD2>
|
||||
{ };
|
||||
static_assert(sizeof(Hash2) == 1, "No duplicate bases");
|
53
libstdc++-v3/testsuite/20_util/unique_ptr/hash/types.cc
Normal file
53
libstdc++-v3/testsuite/20_util/unique_ptr/hash/types.cc
Normal file
@ -0,0 +1,53 @@
|
||||
// { dg-do compile { target c++11 } }
|
||||
|
||||
#include <memory>
|
||||
|
||||
|
||||
// Check for presence/absence of nested types.
|
||||
|
||||
template<typename T> using res_type = typename std::hash<T>::result_type;
|
||||
template<typename T> using arg_type = typename std::hash<T>::argument_type;
|
||||
|
||||
template<typename UniqPtr, typename = void>
|
||||
constexpr bool
|
||||
has_res_type(...)
|
||||
{ return false; }
|
||||
|
||||
template<typename UniqPtr>
|
||||
constexpr typename std::is_void<res_type<UniqPtr>>::value_type // i.e. bool
|
||||
has_res_type()
|
||||
{ return true; }
|
||||
|
||||
template<typename UniqPtr, typename = void>
|
||||
constexpr bool
|
||||
has_arg_type(...)
|
||||
{ return false; }
|
||||
|
||||
template<typename UniqPtr>
|
||||
constexpr typename std::is_void<arg_type<UniqPtr>>::value_type // i.e. bool
|
||||
has_arg_type()
|
||||
{ return true; }
|
||||
|
||||
template<typename UniqPtr>
|
||||
constexpr bool
|
||||
has_no_types()
|
||||
{ return ! has_res_type<UniqPtr>() && ! has_arg_type<UniqPtr>(); }
|
||||
|
||||
#if __cplusplus >= 202002L
|
||||
// Nested types result_type and argument_type are not present in C++20
|
||||
static_assert( has_no_types<std::unique_ptr<int>>() );
|
||||
static_assert( has_no_types<std::unique_ptr<double>>() );
|
||||
#else
|
||||
// Nested types result_type and argument_type are deprecated in C++17.
|
||||
using R1 = std::hash<std::unique_ptr<int>>::result_type; // { dg-warning "deprecated" "" { target c++17_only } }
|
||||
using A1 = std::hash<std::unique_ptr<int>>::argument_type; // { dg-warning "deprecated" "" { target c++17_only } }
|
||||
#endif
|
||||
|
||||
struct S { };
|
||||
template<> struct std::hash<S*> // disabled specialization
|
||||
{
|
||||
hash(hash&&) = delete;
|
||||
~hash() = delete;
|
||||
};
|
||||
// Disabled specializations do not have the nested types.
|
||||
static_assert( has_no_types<std::unique_ptr<S>>(), "disabled specialization" );
|
@ -48,3 +48,37 @@ int main()
|
||||
std::variant<int> x2 = 42;
|
||||
VERIFY(std::hash<int>()(x) == std::hash<std::variant<int>>()(x2));
|
||||
}
|
||||
|
||||
// Check for presence/absence of nested types.
|
||||
|
||||
template<typename T> using res_type = typename std::hash<T>::result_type;
|
||||
template<typename T> using arg_type = typename std::hash<T>::argument_type;
|
||||
|
||||
template<typename Variant, typename = void>
|
||||
constexpr bool has_res_type = false;
|
||||
template<typename Variant>
|
||||
constexpr bool has_res_type<Variant, std::void_t<res_type<Variant>>> = true;
|
||||
template<typename Variant, typename = void>
|
||||
constexpr bool has_arg_type = false;
|
||||
template<typename Variant>
|
||||
constexpr bool has_arg_type<Variant, std::void_t<arg_type<Variant>>> = true;
|
||||
|
||||
template<typename... Ts>
|
||||
constexpr bool has_no_types
|
||||
= ! has_res_type<std::variant<Ts...>> && ! has_arg_type<std::variant<Ts...>>;
|
||||
|
||||
#if __cplusplus >= 202002L
|
||||
// Nested types result_type and argument_type are not present in C++20
|
||||
static_assert( has_no_types<int> );
|
||||
static_assert( has_no_types<int, double> );
|
||||
#else
|
||||
// Nested types result_type and argument_type are deprecated in C++17.
|
||||
using R1 = std::hash<std::variant<int>>::result_type; // { dg-warning "deprecated" "" { target c++17_only } }
|
||||
using A1 = std::hash<std::variant<int>>::argument_type; // { dg-warning "deprecated" "" { target c++17_only } }
|
||||
using R2 = std::hash<std::variant<char, int>>::result_type; // { dg-warning "deprecated" "" { target c++17_only } }
|
||||
using A2 = std::hash<std::variant<char, int>>::argument_type; // { dg-warning "deprecated" "" { target c++17_only } }
|
||||
#endif
|
||||
|
||||
// Disabled specializations do not have the nested types.
|
||||
static_assert( has_no_types<S> );
|
||||
static_assert( has_no_types<int, S> );
|
||||
|
48
libstdc++-v3/testsuite/20_util/variant/hash_abi.cc
Normal file
48
libstdc++-v3/testsuite/20_util/variant/hash_abi.cc
Normal file
@ -0,0 +1,48 @@
|
||||
// { dg-options "-Wdeprecated" }
|
||||
// { dg-do compile { target c++17 } }
|
||||
|
||||
#include <variant>
|
||||
|
||||
struct S { }; // std::hash<S> is a disabled specialization.
|
||||
|
||||
// Test std::hash size
|
||||
|
||||
template<typename... Ts>
|
||||
constexpr std::size_t hash_size = sizeof(std::hash<std::variant<Ts...>>);
|
||||
|
||||
#if _GLIBCXX_INLINE_VERSION
|
||||
// For the unstable ABI the size should always be one.
|
||||
template<std::size_t Size, typename... Ts>
|
||||
constexpr bool check_hash_size = hash_size<Ts...> == 1;
|
||||
#else
|
||||
// For the default ABI, the std::hash specialization has sizeof...(Ts)
|
||||
// base classes of types __hash_empty_base<remove_cv_t<Ts>>... and if
|
||||
// the same type occurs more than once they must have unique addresses.
|
||||
template<std::size_t Size, typename... Ts>
|
||||
constexpr bool check_hash_size = hash_size<Ts...> == Size;
|
||||
#endif
|
||||
|
||||
static_assert( check_hash_size<1, int> );
|
||||
static_assert( check_hash_size<1, int, long, double> );
|
||||
static_assert( check_hash_size<2, int, long, int> );
|
||||
static_assert( check_hash_size<2, int, long, const int> );
|
||||
static_assert( check_hash_size<3, int, int, const int> );
|
||||
|
||||
static_assert( check_hash_size<1, S> );
|
||||
static_assert( check_hash_size<1, int, S> );
|
||||
static_assert( check_hash_size<2, int, S, int> );
|
||||
static_assert( check_hash_size<2, int, S, int, S> );
|
||||
static_assert( check_hash_size<2, int, S, S, int> );
|
||||
static_assert( check_hash_size<3, int, S, S, int, S> );
|
||||
|
||||
// For the default ABI this has two __hash_empty_base<int> base classes,
|
||||
// for the unstable ABI it does not.
|
||||
struct H
|
||||
: std::hash<std::variant<int>>, std::hash<std::variant<long, int>>
|
||||
{ };
|
||||
static_assert( sizeof(H) == hash_size<int, int, long> );
|
||||
// Likewise, even though one of the base classes is a disabled specialization.
|
||||
struct HX
|
||||
: std::hash<std::variant<int>>, std::hash<std::variant<S, int>>
|
||||
{ };
|
||||
static_assert( sizeof(HX) == hash_size<int, S, int> );
|
Loading…
Reference in New Issue
Block a user