From 73be7c2ab267778f64c7383b7a8150788ab7e2ef Mon Sep 17 00:00:00 2001 From: greg Date: Sun, 6 Mar 2022 16:54:03 -0500 Subject: [PATCH] manually merge some changes from abseil-cpp --- parallel_hashmap/btree.h | 56 ++++++++++----- parallel_hashmap/phmap.h | 121 +++++++++++++++++++++++++------- parallel_hashmap/phmap_base.h | 7 +- parallel_hashmap/phmap_bits.h | 25 +++---- parallel_hashmap/phmap_config.h | 28 ++++++-- 5 files changed, 174 insertions(+), 63 deletions(-) diff --git a/parallel_hashmap/btree.h b/parallel_hashmap/btree.h index cbfb8ee..e72b4d2 100644 --- a/parallel_hashmap/btree.h +++ b/parallel_hashmap/btree.h @@ -858,14 +858,14 @@ namespace priv { // Upper bound for the available space for values. This is largest for leaf // nodes, which have overhead of at least a pointer + 4 bytes (for storing // 3 field_types and an enum). - kNodeValueSpace = - TargetNodeSize - /*minimum overhead=*/(sizeof(void *) + 4), + kNodeSlotSpace = + TargetNodeSize - /*minimum overhead=*/(sizeof(void *) + 4), }; // This is an integral type large enough to hold as many // ValueSize-values as will fit a node of TargetNodeSize bytes. using node_count_type = - phmap::conditional_t<(kNodeValueSpace / sizeof(value_type) > + phmap::conditional_t<(kNodeSlotSpace / sizeof(slot_type) > (std::numeric_limits::max)()), uint16_t, uint8_t>; // NOLINT @@ -2501,7 +2501,7 @@ namespace priv { "key comparison function must return phmap::{weak,strong}_ordering or " "bool."); - // Test the assumption made in setting kNodeValueSpace. + // Test the assumption made in setting kNodeSlotSpace. static_assert(node_type::MinimumOverhead() >= sizeof(void *) + 4, "node space assumption incorrect"); @@ -3314,6 +3314,12 @@ namespace priv { const_reverse_iterator crend() const { return tree_.rend(); } // Lookup routines. + // ---------------- + template + size_type count(const key_arg &key) const { + auto equal_range = this->equal_range(key); + return std::distance(equal_range.first, equal_range.second); + } template iterator find(const key_arg &key) { return tree_.find(key); @@ -3350,7 +3356,11 @@ namespace priv { iterator erase(const_iterator first, const_iterator last) { return tree_.erase(iterator(first), iterator(last)).second; } - + template + size_type erase(const key_arg &key) { + auto equal_range = this->equal_range(key); + return tree_.erase_range(equal_range.first, equal_range.second).first; + } node_type extract(iterator position) { // Use Move instead of Transfer, because the rebalancing code expects to // have a valid object to scribble metadata bits on top of. @@ -3449,6 +3459,10 @@ namespace priv { const allocator_type &alloc = allocator_type()) : btree_set_container(init.begin(), init.end(), comp, alloc) {} + btree_set_container(std::initializer_list init, + const allocator_type &alloc) + : btree_set_container(init.begin(), init.end(), alloc) {} + // Lookup routines. template size_type count(const key_arg &key) const { @@ -3467,23 +3481,23 @@ namespace priv { init_type v(std::forward(args)...); return this->tree_.insert_unique(params_type::key(v), std::move(v)); } - iterator insert(const_iterator position, const value_type &x) { + iterator insert(const_iterator hint, const value_type &x) { return this->tree_ - .insert_hint_unique(iterator(position), params_type::key(x), x) + .insert_hint_unique(iterator(hint), params_type::key(x), x) .first; } - iterator insert(const_iterator position, value_type &&x) { + iterator insert(const_iterator hint, value_type &&x) { return this->tree_ - .insert_hint_unique(iterator(position), params_type::key(x), + .insert_hint_unique(iterator(hint), params_type::key(x), std::move(x)) .first; } template - iterator emplace_hint(const_iterator position, Args &&... args) { + iterator emplace_hint(const_iterator hint, Args &&... args) { init_type v(std::forward(args)...); return this->tree_ - .insert_hint_unique(iterator(position), params_type::key(v), + .insert_hint_unique(iterator(hint), params_type::key(v), std::move(v)) .first; } @@ -3705,11 +3719,11 @@ namespace priv { iterator insert(value_type &&x) { return this->tree_.insert_multi(std::move(x)); } - iterator insert(const_iterator position, const value_type &x) { - return this->tree_.insert_hint_multi(iterator(position), x); + iterator insert(const_iterator hint, const value_type &x) { + return this->tree_.insert_hint_multi(iterator(hint), x); } - iterator insert(const_iterator position, value_type &&x) { - return this->tree_.insert_hint_multi(iterator(position), std::move(x)); + iterator insert(const_iterator hint, value_type &&x) { + return this->tree_.insert_hint_multi(iterator(hint), std::move(x)); } template void insert(InputIterator b, InputIterator e) { @@ -3723,9 +3737,9 @@ namespace priv { return this->tree_.insert_multi(init_type(std::forward(args)...)); } template - iterator emplace_hint(const_iterator position, Args &&... args) { + iterator emplace_hint(const_iterator hint, Args &&... args) { return this->tree_.insert_hint_multi( - iterator(position), init_type(std::forward(args)...)); + iterator(hint), init_type(std::forward(args)...)); } iterator insert(node_type &&node) { if (!node) return this->end(); @@ -3839,6 +3853,8 @@ namespace priv { using Base::contains; using Base::count; using Base::equal_range; + using Base::lower_bound; + using Base::upper_bound; using Base::find; using Base::get_allocator; using Base::key_comp; @@ -3896,6 +3912,8 @@ namespace priv { using Base::contains; using Base::count; using Base::equal_range; + using Base::lower_bound; + using Base::upper_bound; using Base::find; using Base::get_allocator; using Base::key_comp; @@ -3956,6 +3974,8 @@ namespace priv { using Base::contains; using Base::count; using Base::equal_range; + using Base::lower_bound; + using Base::upper_bound; using Base::find; using Base::operator[]; using Base::get_allocator; @@ -4013,6 +4033,8 @@ namespace priv { using Base::contains; using Base::count; using Base::equal_range; + using Base::lower_bound; + using Base::upper_bound; using Base::find; using Base::get_allocator; using Base::key_comp; diff --git a/parallel_hashmap/phmap.h b/parallel_hashmap/phmap.h index 578fc51..7d081b1 100644 --- a/parallel_hashmap/phmap.h +++ b/parallel_hashmap/phmap.h @@ -34,6 +34,58 @@ // limitations under the License. // --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// IMPLEMENTATION DETAILS +// +// The table stores elements inline in a slot array. In addition to the slot +// array the table maintains some control state per slot. The extra state is one +// byte per slot and stores empty or deleted marks, or alternatively 7 bits from +// the hash of an occupied slot. The table is split into logical groups of +// slots, like so: +// +// Group 1 Group 2 Group 3 +// +---------------+---------------+---------------+ +// | | | | | | | | | | | | | | | | | | | | | | | | | +// +---------------+---------------+---------------+ +// +// On lookup the hash is split into two parts: +// - H2: 7 bits (those stored in the control bytes) +// - H1: the rest of the bits +// The groups are probed using H1. For each group the slots are matched to H2 in +// parallel. Because H2 is 7 bits (128 states) and the number of slots per group +// is low (8 or 16) in almost all cases a match in H2 is also a lookup hit. +// +// On insert, once the right group is found (as in lookup), its slots are +// filled in order. +// +// On erase a slot is cleared. In case the group did not have any empty slots +// before the erase, the erased slot is marked as deleted. +// +// Groups without empty slots (but maybe with deleted slots) extend the probe +// sequence. The probing algorithm is quadratic. Given N the number of groups, +// the probing function for the i'th probe is: +// +// P(0) = H1 % N +// +// P(i) = (P(i - 1) + i) % N +// +// This probing function guarantees that after N probes, all the groups of the +// table will be probed exactly once. +// +// The control state and slot array are stored contiguously in a shared heap +// allocation. The layout of this allocation is: `capacity()` control bytes, +// one sentinel control byte, `Group::kWidth - 1` cloned control bytes, +// , `capacity()` slots. The sentinel control byte is used in +// iteration so we know when we reach the end of the table. The cloned control +// bytes at the end of the table are cloned from the beginning of the table so +// groups that begin near the end of the table can see a full group. In cases in +// which there are more than `capacity()` cloned control bytes, the extra bytes +// are `kEmpty`, and these ensure that we always see at least one empty slot and +// can stop an unsuccessful search. +// --------------------------------------------------------------------------- + + + #ifdef _MSC_VER #pragma warning(push) @@ -76,6 +128,17 @@ namespace phmap { namespace priv { +// -------------------------------------------------------------------------- +template +void SwapAlloc(AllocType& lhs, AllocType& rhs, + std::true_type /* propagate_on_container_swap */) { + using std::swap; + swap(lhs, rhs); +} +template +void SwapAlloc(AllocType& /*lhs*/, AllocType& /*rhs*/, + std::false_type /* propagate_on_container_swap */) {} + // -------------------------------------------------------------------------- template class probe_seq @@ -181,26 +244,24 @@ public: return *this; } explicit operator bool() const { return mask_ != 0; } - int operator*() const { return LowestBitSet(); } - int LowestBitSet() const { + uint32_t operator*() const { return LowestBitSet(); } + uint32_t LowestBitSet() const { return priv::TrailingZeros(mask_) >> Shift; } - int HighestBitSet() const { - return (sizeof(T) * CHAR_BIT - priv::LeadingZeros(mask_) - - 1) >> - Shift; + uint32_t HighestBitSet() const { + return (sizeof(T) * CHAR_BIT - priv::LeadingZeros(mask_) - 1) >> Shift; } BitMask begin() const { return *this; } BitMask end() const { return BitMask(0); } - int TrailingZeros() const { + uint32_t TrailingZeros() const { return priv::TrailingZeros(mask_) >> Shift; } - int LeadingZeros() const { - constexpr int total_significant_bits = SignificantBits << Shift; - constexpr int extra_bits = sizeof(T) * 8 - total_significant_bits; + uint32_t LeadingZeros() const { + constexpr uint32_t total_significant_bits = SignificantBits << Shift; + constexpr uint32_t extra_bits = sizeof(T) * 8 - total_significant_bits; return priv::LeadingZeros(mask_ << extra_bits) >> Shift; } @@ -286,10 +347,10 @@ inline size_t H1(size_t hashval, const ctrl_t* ) { #endif -inline ctrl_t H2(size_t hashval) { return (ctrl_t)(hashval & 0x7F); } +inline h2_t H2(size_t hashval) { return (ctrl_t)(hashval & 0x7F); } inline bool IsEmpty(ctrl_t c) { return c == kEmpty; } -inline bool IsFull(ctrl_t c) { return c >= 0; } +inline bool IsFull(ctrl_t c) { return c >= static_cast(0); } inline bool IsDeleted(ctrl_t c) { return c == kDeleted; } inline bool IsEmptyOrDeleted(ctrl_t c) { return c < kSentinel; } @@ -338,7 +399,7 @@ struct GroupSse2Impl BitMask Match(h2_t hash) const { auto match = _mm_set1_epi8((char)hash); return BitMask( - _mm_movemask_epi8(_mm_cmpeq_epi8(match, ctrl))); + static_cast(_mm_movemask_epi8(_mm_cmpeq_epi8(match, ctrl)))); } // Returns a bitmask representing the positions of empty slots. @@ -347,7 +408,7 @@ struct GroupSse2Impl #if PHMAP_HAVE_SSSE3 // This only works because kEmpty is -128. return BitMask( - _mm_movemask_epi8(_mm_sign_epi8(ctrl, ctrl))); + static_cast(_mm_movemask_epi8(_mm_sign_epi8(ctrl, ctrl)))); #else return Match(static_cast(kEmpty)); #endif @@ -356,17 +417,17 @@ struct GroupSse2Impl // Returns a bitmask representing the positions of empty or deleted slots. // ----------------------------------------------------------------------- BitMask MatchEmptyOrDeleted() const { - auto special = _mm_set1_epi8(kSentinel); + auto special = _mm_set1_epi8(static_cast(kSentinel)); return BitMask( - _mm_movemask_epi8(_mm_cmpgt_epi8_fixed(special, ctrl))); + static_cast(_mm_movemask_epi8(_mm_cmpgt_epi8_fixed(special, ctrl)))); } // Returns the number of trailing empty or deleted elements in the group. // ---------------------------------------------------------------------- uint32_t CountLeadingEmptyOrDeleted() const { - auto special = _mm_set1_epi8(kSentinel); + auto special = _mm_set1_epi8(static_cast(kSentinel)); return TrailingZeros( - _mm_movemask_epi8(_mm_cmpgt_epi8_fixed(special, ctrl)) + 1); + static_cast(_mm_movemask_epi8(_mm_cmpgt_epi8_fixed(special, ctrl)) + 1)); } // ---------------------------------------------------------------------- @@ -453,6 +514,11 @@ struct GroupPortableImpl using Group = GroupPortableImpl; #endif +// The number of cloned control bytes that we copy from the beginning to the +// end of the control bytes array. +// ------------------------------------------------------------------------- +constexpr size_t NumClonedBytes() { return Group::kWidth - 1; } + template class raw_hash_set; @@ -1341,7 +1407,9 @@ public: } iterator insert(const_iterator, node_type&& node) { - return insert(std::move(node)).first; + auto res = insert(std::move(node)); + node = std::move(res.node); + return res.position; } // This overload kicks in if we can deduce the key from args. This enables us @@ -1759,7 +1827,7 @@ private: auto seq = probe(hashval); while (true) { Group g{ ctrl_ + seq.offset() }; - for (int i : g.Match((h2_t)H2(hashval))) { + for (uint32_t i : g.Match((h2_t)H2(hashval))) { offset = seq.offset((size_t)i); if (PHMAP_PREDICT_TRUE(PolicyTraits::apply( EqualElement{key, eq_ref()}, @@ -2033,7 +2101,7 @@ private: auto seq = probe(hashval); while (true) { Group g{ctrl_ + seq.offset()}; - for (int i : g.Match((h2_t)H2(hashval))) { + for (uint32_t i : g.Match((h2_t)H2(hashval))) { if (PHMAP_PREDICT_TRUE(PolicyTraits::element(slots_ + seq.offset((size_t)i)) == elem)) return true; @@ -2095,7 +2163,7 @@ protected: auto seq = probe(hashval); while (true) { Group g{ctrl_ + seq.offset()}; - for (int i : g.Match((h2_t)H2(hashval))) { + for (uint32_t i : g.Match((h2_t)H2(hashval))) { if (PHMAP_PREDICT_TRUE(PolicyTraits::apply( EqualElement{key, eq_ref()}, PolicyTraits::element(slots_ + seq.offset((size_t)i))))) @@ -2261,9 +2329,10 @@ public: using key_arg = typename KeyArgImpl::template type; static_assert(!std::is_reference::value, ""); - // TODO(alkis): remove this assertion and verify that reference mapped_type is - // supported. - static_assert(!std::is_reference::value, ""); + + // TODO(b/187807849): Evaluate whether to support reference mapped_type and + // remove this assertion if/when it is supported. + static_assert(!std::is_reference::value, ""); using iterator = typename raw_hash_map::raw_hash_set::iterator; using const_iterator = typename raw_hash_map::raw_hash_set::const_iterator; @@ -4327,7 +4396,7 @@ struct HashtableDebugAccess> auto seq = set.probe(hashval); while (true) { priv::Group g{set.ctrl_ + seq.offset()}; - for (int i : g.Match(priv::H2(hashval))) { + for (uint32_t i : g.Match(priv::H2(hashval))) { if (Traits::apply( typename Set::template EqualElement{ key, set.eq_ref()}, diff --git a/parallel_hashmap/phmap_base.h b/parallel_hashmap/phmap_base.h index 3320f5d..a972921 100644 --- a/parallel_hashmap/phmap_base.h +++ b/parallel_hashmap/phmap_base.h @@ -2856,6 +2856,7 @@ class node_handle { using Base = node_handle_base; + using slot_type = typename PolicyTraits::slot_type; public: using key_type = typename Policy::key_type; @@ -3888,7 +3889,7 @@ public: constexpr size_t Offset() const { static_assert(N < NumOffsets, "Index out of bounds"); return adl_barrier::Align( - Offset() + SizeOf>() * size_[N - 1], + Offset() + SizeOf>::value * size_[N - 1], ElementAlignment::value); } @@ -4081,7 +4082,7 @@ public: constexpr size_t AllocSize() const { static_assert(NumTypes == NumSizes, "You must specify sizes of all fields"); return Offset() + - SizeOf>() * size_[NumTypes - 1]; + SizeOf>::value * size_[NumTypes - 1]; } // If built with --config=asan, poisons padding bytes (if any) in the @@ -4105,7 +4106,7 @@ public: // The `if` is an optimization. It doesn't affect the observable behaviour. if (ElementAlignment::value % ElementAlignment::value) { size_t start = - Offset() + SizeOf>() * size_[N - 1]; + Offset() + SizeOf>::value * size_[N - 1]; ASAN_POISON_MEMORY_REGION(p + start, Offset() - start); } #endif diff --git a/parallel_hashmap/phmap_bits.h b/parallel_hashmap/phmap_bits.h index 6b765ff..d37ede3 100644 --- a/parallel_hashmap/phmap_bits.h +++ b/parallel_hashmap/phmap_bits.h @@ -315,19 +315,19 @@ PHMAP_BASE_INTERNAL_FORCEINLINE int CountLeadingZeros64(uint64_t n) { #endif } -PHMAP_BASE_INTERNAL_FORCEINLINE int CountLeadingZeros32Slow(uint64_t n) { - int zeroes = 28; +PHMAP_BASE_INTERNAL_FORCEINLINE uint32_t CountLeadingZeros32Slow(uint64_t n) { + uint32_t zeroes = 28; if (n >> 16) zeroes -= 16, n >>= 16; if (n >> 8) zeroes -= 8, n >>= 8; if (n >> 4) zeroes -= 4, n >>= 4; return "\4\3\2\2\1\1\1\1\0\0\0\0\0\0\0"[n] + zeroes; } -PHMAP_BASE_INTERNAL_FORCEINLINE int CountLeadingZeros32(uint32_t n) { +PHMAP_BASE_INTERNAL_FORCEINLINE uint32_t CountLeadingZeros32(uint32_t n) { #if defined(_MSC_VER) && !defined(__clang__) unsigned long result = 0; // NOLINT(runtime/int) if (_BitScanReverse(&result, n)) { - return (int)(31 - result); + return (uint32_t)(31 - result); } return 32; #elif defined(__GNUC__) || defined(__clang__) @@ -348,8 +348,8 @@ PHMAP_BASE_INTERNAL_FORCEINLINE int CountLeadingZeros32(uint32_t n) { #endif } -PHMAP_BASE_INTERNAL_FORCEINLINE int CountTrailingZerosNonZero64Slow(uint64_t n) { - int c = 63; +PHMAP_BASE_INTERNAL_FORCEINLINE uint32_t CountTrailingZerosNonZero64Slow(uint64_t n) { + uint32_t c = 63; n &= ~n + 1; if (n & 0x00000000FFFFFFFF) c -= 32; if (n & 0x0000FFFF0000FFFF) c -= 16; @@ -360,11 +360,11 @@ PHMAP_BASE_INTERNAL_FORCEINLINE int CountTrailingZerosNonZero64Slow(uint64_t n) return c; } -PHMAP_BASE_INTERNAL_FORCEINLINE int CountTrailingZerosNonZero64(uint64_t n) { +PHMAP_BASE_INTERNAL_FORCEINLINE uint32_t CountTrailingZerosNonZero64(uint64_t n) { #if defined(_MSC_VER) && !defined(__clang__) && defined(_M_X64) unsigned long result = 0; // NOLINT(runtime/int) _BitScanForward64(&result, n); - return (int)result; + return (uint32_t)result; #elif defined(_MSC_VER) && !defined(__clang__) unsigned long result = 0; // NOLINT(runtime/int) if (static_cast(n) == 0) { @@ -382,8 +382,8 @@ PHMAP_BASE_INTERNAL_FORCEINLINE int CountTrailingZerosNonZero64(uint64_t n) { #endif } -PHMAP_BASE_INTERNAL_FORCEINLINE int CountTrailingZerosNonZero32Slow(uint32_t n) { - int c = 31; +PHMAP_BASE_INTERNAL_FORCEINLINE uint32_t CountTrailingZerosNonZero32Slow(uint32_t n) { + uint32_t c = 31; n &= ~n + 1; if (n & 0x0000FFFF) c -= 16; if (n & 0x00FF00FF) c -= 8; @@ -393,11 +393,11 @@ PHMAP_BASE_INTERNAL_FORCEINLINE int CountTrailingZerosNonZero32Slow(uint32_t n) return c; } -PHMAP_BASE_INTERNAL_FORCEINLINE int CountTrailingZerosNonZero32(uint32_t n) { +PHMAP_BASE_INTERNAL_FORCEINLINE uint32_t CountTrailingZerosNonZero32(uint32_t n) { #if defined(_MSC_VER) && !defined(__clang__) unsigned long result = 0; // NOLINT(runtime/int) _BitScanForward(&result, n); - return (int)result; + return (uint32_t)result; #elif defined(__GNUC__) || defined(__clang__) static_assert(sizeof(int) == sizeof(n), "__builtin_ctz does not take 32-bit arg"); @@ -568,6 +568,7 @@ namespace little_endian { #endif /* ENDIAN */ // Functions to do unaligned loads and stores in little-endian order. +// ------------------------------------------------------------------ inline uint16_t Load16(const void *p) { return ToHost16(PHMAP_INTERNAL_UNALIGNED_LOAD16(p)); } diff --git a/parallel_hashmap/phmap_config.h b/parallel_hashmap/phmap_config.h index fa51502..45af40c 100644 --- a/parallel_hashmap/phmap_config.h +++ b/parallel_hashmap/phmap_config.h @@ -128,15 +128,33 @@ #define PHMAP_BRANCHLESS 1 +#ifdef __has_feature +#define PHMAP_HAVE_FEATURE(f) __has_feature(f) +#else +#define PHMAP_HAVE_FEATURE(f) 0 +#endif + +// Portable check for GCC minimum version: +// https://gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html +#if defined(__GNUC__) && defined(__GNUC_MINOR__) + #define PHMAP_INTERNAL_HAVE_MIN_GNUC_VERSION(x, y) (__GNUC__ > (x) || __GNUC__ == (x) && __GNUC_MINOR__ >= (y)) +#else + #define PHMAP_INTERNAL_HAVE_MIN_GNUC_VERSION(x, y) 0 +#endif + +#if defined(__clang__) && defined(__clang_major__) && defined(__clang_minor__) + #define PHMAP_INTERNAL_HAVE_MIN_CLANG_VERSION(x, y) (__clang_major__ > (x) || __clang_major__ == (x) && __clang_minor__ >= (y)) +#else + #define PHMAP_INTERNAL_HAVE_MIN_CLANG_VERSION(x, y) 0 +#endif + // ---------------------------------------------------------------- // Checks whether `std::is_trivially_destructible` is supported. // ---------------------------------------------------------------- #ifdef PHMAP_HAVE_STD_IS_TRIVIALLY_DESTRUCTIBLE #error PHMAP_HAVE_STD_IS_TRIVIALLY_DESTRUCTIBLE cannot be directly set -#elif defined(_LIBCPP_VERSION) || \ - (!defined(__clang__) && defined(__GNUC__) && defined(__GLIBCXX__) && \ - (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8))) || \ - defined(_MSC_VER) +#elif defined(_LIBCPP_VERSION) || defined(_MSC_VER) || \ + (!defined(__clang__) && defined(__GNUC__) && defined(__GLIBCXX__) && PHMAP_INTERNAL_HAVE_MIN_GNUC_VERSION(4, 8)) #define PHMAP_HAVE_STD_IS_TRIVIALLY_DESTRUCTIBLE 1 #endif @@ -150,7 +168,7 @@ #error PHMAP_HAVE_STD_IS_TRIVIALLY_ASSIGNABLE cannot directly set #elif (defined(__clang__) && defined(_LIBCPP_VERSION)) || \ (!defined(__clang__) && defined(__GNUC__) && \ - (__GNUC__ > 5 || (__GNUC__ == 5 && __GNUC_MINOR__ >= 1)) && \ + PHMAP_INTERNAL_HAVE_MIN_GNUC_VERSION(5, 1) && \ (defined(_LIBCPP_VERSION) || defined(__GLIBCXX__))) || \ (defined(_MSC_VER) && !defined(__NVCC__)) #define PHMAP_HAVE_STD_IS_TRIVIALLY_CONSTRUCTIBLE 1