From cd2aa7a6586afd0b66c5aa2fa157c2a5ed7f0e94 Mon Sep 17 00:00:00 2001 From: Manu Goyal Date: Sun, 23 Dec 2018 20:57:12 -0800 Subject: [PATCH] Allow hashtable to work with incomplete types. Experimentally verified using Ubuntu g++-5.4 and Apple clang-800.0.42.1. The main issue is that some of our type_traits checks get instantiated in compilation before the type is fully defined, which leads to a compilation error. So we play some tricks to try and get them instantiated later. 1) declare operator<< and operator>> as friends to a generic KeyType and TType, and use std::is_trivial on the function-specific templates. That way, the types are instantiated after the class is defined. 2) Turn the is_simple and is_data_nothrow_move_constructible cosntexpr booleans into constexpr methods. This seems to let them get instantiated later. This also lets us use an incomplete key type, if so desired. I'm not really sure if these fixes are future-proof, because they seem to rely on compiler implementation details, and not some standards-documented behavior, though I haven't really looked through the standard to verify. --- libcuckoo/cuckoohash_map.hh | 20 +++++++++++--------- libcuckoo/libcuckoo_bucket_container.hh | 20 ++++++++++++-------- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/libcuckoo/cuckoohash_map.hh b/libcuckoo/cuckoohash_map.hh index bebf9045..aeb341e5 100644 --- a/libcuckoo/cuckoohash_map.hh +++ b/libcuckoo/cuckoohash_map.hh @@ -725,13 +725,15 @@ private: // true if the key is small and simple, which means using partial keys for // lookup would probably slow us down - static constexpr bool is_simple = - std::is_pod::value && sizeof(key_type) <= 8; + static constexpr bool is_simple() { + return std::is_pod::value && sizeof(key_type) <= 8; + } // Whether or not the data is nothrow-move-constructible. - static constexpr bool is_data_nothrow_move_constructible = - std::is_nothrow_move_constructible::value && - std::is_nothrow_move_constructible::value; + static constexpr bool is_data_nothrow_move_constructible() { + return std::is_nothrow_move_constructible::value && + std::is_nothrow_move_constructible::value; + } // Contains a hash and partial for a given key. The partial key is used for // partial-key cuckoohashing, and for finding the alternate bucket of that a @@ -955,7 +957,7 @@ private: spinlock &lock = locks[l]; if (lock.is_migrated()) return; - assert(is_data_nothrow_move_constructible); + assert(is_data_nothrow_move_constructible()); assert(locks.size() == kMaxNumLocks); assert(old_buckets_.hashpower() + 1 == buckets_.hashpower()); assert(old_buckets_.size() >= kMaxNumLocks); @@ -1164,7 +1166,7 @@ private: // Silence a warning from MSVC about partial being unused if is_simple. (void)partial; for (int i = 0; i < static_cast(slot_per_bucket()); ++i) { - if (!b.occupied(i) || (!is_simple && partial != b.partial(i))) { + if (!b.occupied(i) || (!is_simple() && partial != b.partial(i))) { continue; } else if (key_eq()(b.key(i), key)) { return i; @@ -1311,7 +1313,7 @@ private: slot = -1; for (int i = 0; i < static_cast(slot_per_bucket()); ++i) { if (b.occupied(i)) { - if (!is_simple && partial != b.partial(i)) { + if (!is_simple() && partial != b.partial(i)) { continue; } if (key_eq()(b.key(i), key)) { @@ -1673,7 +1675,7 @@ private: // provides a strong exception guarantee. template cuckoo_status cuckoo_fast_double(size_type current_hp) { - if (!is_data_nothrow_move_constructible) { + if (!is_data_nothrow_move_constructible()) { LIBCUCKOO_DBG("%s", "cannot run cuckoo_fast_double because key-value" " pair is not nothrow move constructible"); return cuckoo_expand_simple(current_hp + 1); diff --git a/libcuckoo/libcuckoo_bucket_container.hh b/libcuckoo/libcuckoo_bucket_container.hh index 1e7a9f84..32f495d2 100644 --- a/libcuckoo/libcuckoo_bucket_container.hh +++ b/libcuckoo/libcuckoo_bucket_container.hh @@ -354,11 +354,13 @@ private: // this should be okay. We could in theory just check if the type is // TriviallyCopyable but this check is not available on some compilers we // want to support. - template - friend typename std::enable_if::value && - std::is_trivial::value, + template + friend typename std::enable_if::value && + std::is_trivial::value, std::ostream &>::type - operator<<(std::ostream &os, const libcuckoo_bucket_container &bc) { + operator<<(std::ostream &os, + const libcuckoo_bucket_container &bc) { size_type hp = bc.hashpower(); os.write(reinterpret_cast(&hp), sizeof(size_type)); os.write(reinterpret_cast(bc.buckets_), @@ -366,11 +368,13 @@ private: return os; } - template - friend typename std::enable_if::value && - std::is_trivial::value, + template + friend typename std::enable_if::value && + std::is_trivial::value, std::istream &>::type - operator>>(std::istream &is, libcuckoo_bucket_container &bc) { + operator>>(std::istream &is, + libcuckoo_bucket_container &bc) { size_type hp; is.read(reinterpret_cast(&hp), sizeof(size_type)); libcuckoo_bucket_container new_bc(hp, bc.get_allocator());