Commit 6879e28c by Evan Brown Committed by Copybara-Service

Make data members of CommonFields be private so that it's easier to change how…

Make data members of CommonFields be private so that it's easier to change how we store this information internally.

PiperOrigin-RevId: 544667586
Change-Id: I9b1943ca71ea1c1f8699832422cd7bc095ac8b2d
parent d9a01008
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
#include "absl/container/internal/raw_hash_set.h" #include "absl/container/internal/raw_hash_set.h"
#include <atomic> #include <atomic>
#include <cassert>
#include <cstddef> #include <cstddef>
#include <cstring> #include <cstring>
...@@ -130,8 +131,8 @@ static inline void* PrevSlot(void* slot, size_t slot_size) { ...@@ -130,8 +131,8 @@ static inline void* PrevSlot(void* slot, size_t slot_size) {
void DropDeletesWithoutResize(CommonFields& common, void DropDeletesWithoutResize(CommonFields& common,
const PolicyFunctions& policy, void* tmp_space) { const PolicyFunctions& policy, void* tmp_space) {
void* set = &common; void* set = &common;
void* slot_array = common.slots_; void* slot_array = common.slots();
const size_t capacity = common.capacity_; const size_t capacity = common.capacity();
assert(IsValidCapacity(capacity)); assert(IsValidCapacity(capacity));
assert(!is_small(capacity)); assert(!is_small(capacity));
// Algorithm: // Algorithm:
...@@ -150,7 +151,7 @@ void DropDeletesWithoutResize(CommonFields& common, ...@@ -150,7 +151,7 @@ void DropDeletesWithoutResize(CommonFields& common,
// swap current element with target element // swap current element with target element
// mark target as FULL // mark target as FULL
// repeat procedure for current slot with moved from element (target) // repeat procedure for current slot with moved from element (target)
ctrl_t* ctrl = common.control_; ctrl_t* ctrl = common.control();
ConvertDeletedToEmptyAndFullToDeleted(ctrl, capacity); ConvertDeletedToEmptyAndFullToDeleted(ctrl, capacity);
auto hasher = policy.hash_slot; auto hasher = policy.hash_slot;
auto transfer = policy.transfer; auto transfer = policy.transfer;
...@@ -210,11 +211,11 @@ void DropDeletesWithoutResize(CommonFields& common, ...@@ -210,11 +211,11 @@ void DropDeletesWithoutResize(CommonFields& common,
void EraseMetaOnly(CommonFields& c, ctrl_t* it, size_t slot_size) { void EraseMetaOnly(CommonFields& c, ctrl_t* it, size_t slot_size) {
assert(IsFull(*it) && "erasing a dangling iterator"); assert(IsFull(*it) && "erasing a dangling iterator");
--c.size_; c.set_size(c.size() - 1);
const auto index = static_cast<size_t>(it - c.control_); const auto index = static_cast<size_t>(it - c.control());
const size_t index_before = (index - Group::kWidth) & c.capacity_; const size_t index_before = (index - Group::kWidth) & c.capacity();
const auto empty_after = Group(it).MaskEmpty(); const auto empty_after = Group(it).MaskEmpty();
const auto empty_before = Group(c.control_ + index_before).MaskEmpty(); const auto empty_before = Group(c.control() + index_before).MaskEmpty();
// We count how many consecutive non empties we have to the right and to the // We count how many consecutive non empties we have to the right and to the
// left of `it`. If the sum is >= kWidth then there is at least one probe // left of `it`. If the sum is >= kWidth then there is at least one probe
...@@ -226,26 +227,26 @@ void EraseMetaOnly(CommonFields& c, ctrl_t* it, size_t slot_size) { ...@@ -226,26 +227,26 @@ void EraseMetaOnly(CommonFields& c, ctrl_t* it, size_t slot_size) {
SetCtrl(c, index, was_never_full ? ctrl_t::kEmpty : ctrl_t::kDeleted, SetCtrl(c, index, was_never_full ? ctrl_t::kEmpty : ctrl_t::kDeleted,
slot_size); slot_size);
c.growth_left() += (was_never_full ? 1 : 0); c.set_growth_left(c.growth_left() + (was_never_full ? 1 : 0));
c.infoz().RecordErase(); c.infoz().RecordErase();
} }
void ClearBackingArray(CommonFields& c, const PolicyFunctions& policy, void ClearBackingArray(CommonFields& c, const PolicyFunctions& policy,
bool reuse) { bool reuse) {
c.size_ = 0; c.set_size(0);
if (reuse) { if (reuse) {
ResetCtrl(c, policy.slot_size); ResetCtrl(c, policy.slot_size);
c.infoz().RecordStorageChanged(0, c.capacity_); c.infoz().RecordStorageChanged(0, c.capacity());
} else { } else {
void* set = &c; void* set = &c;
(*policy.dealloc)(set, policy, c.control_, c.slots_, c.capacity_); (*policy.dealloc)(set, policy, c.control(), c.slots(), c.capacity());
c.control_ = EmptyGroup(); c.set_control(EmptyGroup());
c.set_generation_ptr(EmptyGeneration()); c.set_generation_ptr(EmptyGeneration());
c.slots_ = nullptr; c.set_slots(nullptr);
c.capacity_ = 0; c.set_capacity(0);
c.growth_left() = 0; c.set_growth_left(0);
c.infoz().RecordClearedReservation(); c.infoz().RecordClearedReservation();
assert(c.size_ == 0); assert(c.size() == 0);
c.infoz().RecordStorageChanged(0, 0); c.infoz().RecordStorageChanged(0, 0);
} }
} }
......
...@@ -889,6 +889,11 @@ using CommonFieldsGenerationInfo = CommonFieldsGenerationInfoDisabled; ...@@ -889,6 +889,11 @@ using CommonFieldsGenerationInfo = CommonFieldsGenerationInfoDisabled;
using HashSetIteratorGenerationInfo = HashSetIteratorGenerationInfoDisabled; using HashSetIteratorGenerationInfo = HashSetIteratorGenerationInfoDisabled;
#endif #endif
// Returns whether `n` is a valid capacity (i.e., number of slots).
//
// A valid capacity is a non-zero integer `2^m - 1`.
inline bool IsValidCapacity(size_t n) { return ((n + 1) & n) == 0 && n > 0; }
// CommonFields hold the fields in raw_hash_set that do not depend // CommonFields hold the fields in raw_hash_set that do not depend
// on template parameters. This allows us to conveniently pass all // on template parameters. This allows us to conveniently pass all
// of this state to helper functions as a single argument. // of this state to helper functions as a single argument.
...@@ -906,21 +911,34 @@ class CommonFields : public CommonFieldsGenerationInfo { ...@@ -906,21 +911,34 @@ class CommonFields : public CommonFieldsGenerationInfo {
std::move(static_cast<CommonFieldsGenerationInfo&&>(that))), std::move(static_cast<CommonFieldsGenerationInfo&&>(that))),
// Explicitly copying fields into "this" and then resetting "that" // Explicitly copying fields into "this" and then resetting "that"
// fields generates less code then calling absl::exchange per field. // fields generates less code then calling absl::exchange per field.
control_(that.control_), control_(that.control()),
slots_(that.slots_), slots_(that.slots()),
size_(that.size_), size_(that.size()),
capacity_(that.capacity_), capacity_(that.capacity()),
compressed_tuple_(that.growth_left(), std::move(that.infoz())) { compressed_tuple_(that.growth_left(), std::move(that.infoz())) {
that.control_ = EmptyGroup(); that.set_control(EmptyGroup());
that.slots_ = nullptr; that.set_slots(nullptr);
that.size_ = 0; that.set_size(0);
that.capacity_ = 0; that.set_capacity(0);
that.growth_left() = 0; that.set_growth_left(0);
} }
CommonFields& operator=(CommonFields&&) = default; CommonFields& operator=(CommonFields&&) = default;
ctrl_t* control() const { return control_; }
void set_control(ctrl_t* c) { control_ = c; }
void* slots() const { return slots_; }
void set_slots(void* s) { slots_ = s; }
size_t size() const { return size_; }
void set_size(size_t s) { size_ = s; }
size_t capacity() const { return capacity_; }
void set_capacity(size_t c) {
assert(c == 0 || IsValidCapacity(c));
capacity_ = c;
}
// The number of slots we can still fill without needing to rehash. // The number of slots we can still fill without needing to rehash.
size_t& growth_left() { return compressed_tuple_.template get<0>(); } size_t growth_left() const { return compressed_tuple_.template get<0>(); }
void set_growth_left(size_t gl) { compressed_tuple_.template get<0>() = gl; }
HashtablezInfoHandle& infoz() { return compressed_tuple_.template get<1>(); } HashtablezInfoHandle& infoz() { return compressed_tuple_.template get<1>(); }
const HashtablezInfoHandle& infoz() const { const HashtablezInfoHandle& infoz() const {
...@@ -929,15 +947,17 @@ class CommonFields : public CommonFieldsGenerationInfo { ...@@ -929,15 +947,17 @@ class CommonFields : public CommonFieldsGenerationInfo {
bool should_rehash_for_bug_detection_on_insert() const { bool should_rehash_for_bug_detection_on_insert() const {
return CommonFieldsGenerationInfo:: return CommonFieldsGenerationInfo::
should_rehash_for_bug_detection_on_insert(control_, capacity_); should_rehash_for_bug_detection_on_insert(control(), capacity());
} }
void reset_reserved_growth(size_t reservation) { void reset_reserved_growth(size_t reservation) {
CommonFieldsGenerationInfo::reset_reserved_growth(reservation, size_); CommonFieldsGenerationInfo::reset_reserved_growth(reservation, size_);
} }
private:
// TODO(b/259599413): Investigate removing some of these fields: // TODO(b/259599413): Investigate removing some of these fields:
// - control/slots can be derived from each other // - control/slots can be derived from each other
// - size can be moved into the slot array // - growth_left can be moved into the slot array
// - we can use 6 bits for capacity since it's always a power of two minus 1
// The control bytes (and, also, a pointer to the base of the backing array). // The control bytes (and, also, a pointer to the base of the backing array).
// //
...@@ -971,11 +991,6 @@ constexpr size_t NumClonedBytes() { return Group::kWidth - 1; } ...@@ -971,11 +991,6 @@ constexpr size_t NumClonedBytes() { return Group::kWidth - 1; }
template <class Policy, class Hash, class Eq, class Alloc> template <class Policy, class Hash, class Eq, class Alloc>
class raw_hash_set; class raw_hash_set;
// Returns whether `n` is a valid capacity (i.e., number of slots).
//
// A valid capacity is a non-zero integer `2^m - 1`.
inline bool IsValidCapacity(size_t n) { return ((n + 1) & n) == 0 && n > 0; }
// Returns the next valid capacity after `n`. // Returns the next valid capacity after `n`.
inline size_t NextCapacity(size_t n) { inline size_t NextCapacity(size_t n) {
assert(IsValidCapacity(n) || n == 0); assert(IsValidCapacity(n) || n == 0);
...@@ -1216,7 +1231,7 @@ inline probe_seq<Group::kWidth> probe(const ctrl_t* ctrl, const size_t capacity, ...@@ -1216,7 +1231,7 @@ inline probe_seq<Group::kWidth> probe(const ctrl_t* ctrl, const size_t capacity,
return probe_seq<Group::kWidth>(H1(hash, ctrl), capacity); return probe_seq<Group::kWidth>(H1(hash, ctrl), capacity);
} }
inline probe_seq<Group::kWidth> probe(const CommonFields& common, size_t hash) { inline probe_seq<Group::kWidth> probe(const CommonFields& common, size_t hash) {
return probe(common.control_, common.capacity_, hash); return probe(common.control(), common.capacity(), hash);
} }
// Probes an array of control bits using a probe sequence derived from `hash`, // Probes an array of control bits using a probe sequence derived from `hash`,
...@@ -1229,7 +1244,7 @@ inline probe_seq<Group::kWidth> probe(const CommonFields& common, size_t hash) { ...@@ -1229,7 +1244,7 @@ inline probe_seq<Group::kWidth> probe(const CommonFields& common, size_t hash) {
template <typename = void> template <typename = void>
inline FindInfo find_first_non_full(const CommonFields& common, size_t hash) { inline FindInfo find_first_non_full(const CommonFields& common, size_t hash) {
auto seq = probe(common, hash); auto seq = probe(common, hash);
const ctrl_t* ctrl = common.control_; const ctrl_t* ctrl = common.control();
while (true) { while (true) {
Group g{ctrl + seq.offset()}; Group g{ctrl + seq.offset()};
auto mask = g.MaskEmptyOrDeleted(); auto mask = g.MaskEmptyOrDeleted();
...@@ -1239,14 +1254,14 @@ inline FindInfo find_first_non_full(const CommonFields& common, size_t hash) { ...@@ -1239,14 +1254,14 @@ inline FindInfo find_first_non_full(const CommonFields& common, size_t hash) {
// In debug build we will randomly insert in either the front or back of // In debug build we will randomly insert in either the front or back of
// the group. // the group.
// TODO(kfm,sbenza): revisit after we do unconditional mixing // TODO(kfm,sbenza): revisit after we do unconditional mixing
if (!is_small(common.capacity_) && ShouldInsertBackwards(hash, ctrl)) { if (!is_small(common.capacity()) && ShouldInsertBackwards(hash, ctrl)) {
return {seq.offset(mask.HighestBitSet()), seq.index()}; return {seq.offset(mask.HighestBitSet()), seq.index()};
} }
#endif #endif
return {seq.offset(mask.LowestBitSet()), seq.index()}; return {seq.offset(mask.LowestBitSet()), seq.index()};
} }
seq.next(); seq.next();
assert(seq.index() <= common.capacity_ && "full table!"); assert(seq.index() <= common.capacity() && "full table!");
} }
} }
...@@ -1260,18 +1275,18 @@ extern template FindInfo find_first_non_full(const CommonFields&, size_t); ...@@ -1260,18 +1275,18 @@ extern template FindInfo find_first_non_full(const CommonFields&, size_t);
FindInfo find_first_non_full_outofline(const CommonFields&, size_t); FindInfo find_first_non_full_outofline(const CommonFields&, size_t);
inline void ResetGrowthLeft(CommonFields& common) { inline void ResetGrowthLeft(CommonFields& common) {
common.growth_left() = CapacityToGrowth(common.capacity_) - common.size_; common.set_growth_left(CapacityToGrowth(common.capacity()) - common.size());
} }
// Sets `ctrl` to `{kEmpty, kSentinel, ..., kEmpty}`, marking the entire // Sets `ctrl` to `{kEmpty, kSentinel, ..., kEmpty}`, marking the entire
// array as marked as empty. // array as marked as empty.
inline void ResetCtrl(CommonFields& common, size_t slot_size) { inline void ResetCtrl(CommonFields& common, size_t slot_size) {
const size_t capacity = common.capacity_; const size_t capacity = common.capacity();
ctrl_t* ctrl = common.control_; ctrl_t* ctrl = common.control();
std::memset(ctrl, static_cast<int8_t>(ctrl_t::kEmpty), std::memset(ctrl, static_cast<int8_t>(ctrl_t::kEmpty),
capacity + 1 + NumClonedBytes()); capacity + 1 + NumClonedBytes());
ctrl[capacity] = ctrl_t::kSentinel; ctrl[capacity] = ctrl_t::kSentinel;
SanitizerPoisonMemoryRegion(common.slots_, slot_size * capacity); SanitizerPoisonMemoryRegion(common.slots(), slot_size * capacity);
ResetGrowthLeft(common); ResetGrowthLeft(common);
} }
...@@ -1281,17 +1296,17 @@ inline void ResetCtrl(CommonFields& common, size_t slot_size) { ...@@ -1281,17 +1296,17 @@ inline void ResetCtrl(CommonFields& common, size_t slot_size) {
// mirror the value to the cloned tail if necessary. // mirror the value to the cloned tail if necessary.
inline void SetCtrl(const CommonFields& common, size_t i, ctrl_t h, inline void SetCtrl(const CommonFields& common, size_t i, ctrl_t h,
size_t slot_size) { size_t slot_size) {
const size_t capacity = common.capacity_; const size_t capacity = common.capacity();
assert(i < capacity); assert(i < capacity);
auto* slot_i = static_cast<const char*>(common.slots_) + i * slot_size; auto* slot_i = static_cast<const char*>(common.slots()) + i * slot_size;
if (IsFull(h)) { if (IsFull(h)) {
SanitizerUnpoisonMemoryRegion(slot_i, slot_size); SanitizerUnpoisonMemoryRegion(slot_i, slot_size);
} else { } else {
SanitizerPoisonMemoryRegion(slot_i, slot_size); SanitizerPoisonMemoryRegion(slot_i, slot_size);
} }
ctrl_t* ctrl = common.control_; ctrl_t* ctrl = common.control();
ctrl[i] = h; ctrl[i] = h;
ctrl[((i - NumClonedBytes()) & capacity) + (NumClonedBytes() & capacity)] = h; ctrl[((i - NumClonedBytes()) & capacity) + (NumClonedBytes() & capacity)] = h;
} }
...@@ -1327,31 +1342,31 @@ inline size_t AllocSize(size_t capacity, size_t slot_size, size_t slot_align) { ...@@ -1327,31 +1342,31 @@ inline size_t AllocSize(size_t capacity, size_t slot_size, size_t slot_align) {
template <typename Alloc, size_t SizeOfSlot, size_t AlignOfSlot> template <typename Alloc, size_t SizeOfSlot, size_t AlignOfSlot>
ABSL_ATTRIBUTE_NOINLINE void InitializeSlots(CommonFields& c, Alloc alloc) { ABSL_ATTRIBUTE_NOINLINE void InitializeSlots(CommonFields& c, Alloc alloc) {
assert(c.capacity_); assert(c.capacity());
// Folks with custom allocators often make unwarranted assumptions about the // Folks with custom allocators often make unwarranted assumptions about the
// behavior of their classes vis-a-vis trivial destructability and what // behavior of their classes vis-a-vis trivial destructability and what
// calls they will or won't make. Avoid sampling for people with custom // calls they will or won't make. Avoid sampling for people with custom
// allocators to get us out of this mess. This is not a hard guarantee but // allocators to get us out of this mess. This is not a hard guarantee but
// a workaround while we plan the exact guarantee we want to provide. // a workaround while we plan the exact guarantee we want to provide.
const size_t sample_size = const size_t sample_size =
(std::is_same<Alloc, std::allocator<char>>::value && c.slots_ == nullptr) (std::is_same<Alloc, std::allocator<char>>::value && c.slots() == nullptr)
? SizeOfSlot ? SizeOfSlot
: 0; : 0;
const size_t cap = c.capacity_; const size_t cap = c.capacity();
char* mem = static_cast<char*>( char* mem = static_cast<char*>(
Allocate<AlignOfSlot>(&alloc, AllocSize(cap, SizeOfSlot, AlignOfSlot))); Allocate<AlignOfSlot>(&alloc, AllocSize(cap, SizeOfSlot, AlignOfSlot)));
const GenerationType old_generation = c.generation(); const GenerationType old_generation = c.generation();
c.set_generation_ptr( c.set_generation_ptr(
reinterpret_cast<GenerationType*>(mem + GenerationOffset(cap))); reinterpret_cast<GenerationType*>(mem + GenerationOffset(cap)));
c.set_generation(NextGeneration(old_generation)); c.set_generation(NextGeneration(old_generation));
c.control_ = reinterpret_cast<ctrl_t*>(mem); c.set_control(reinterpret_cast<ctrl_t*>(mem));
c.slots_ = mem + SlotOffset(cap, AlignOfSlot); c.set_slots(mem + SlotOffset(cap, AlignOfSlot));
ResetCtrl(c, SizeOfSlot); ResetCtrl(c, SizeOfSlot);
if (sample_size) { if (sample_size) {
c.infoz() = Sample(sample_size); c.infoz() = Sample(sample_size);
} }
c.infoz().RecordStorageChanged(c.size_, cap); c.infoz().RecordStorageChanged(c.size(), cap);
} }
// PolicyFunctions bundles together some information for a particular // PolicyFunctions bundles together some information for a particular
...@@ -1654,7 +1669,7 @@ class raw_hash_set { ...@@ -1654,7 +1669,7 @@ class raw_hash_set {
const allocator_type& alloc = allocator_type()) const allocator_type& alloc = allocator_type())
: settings_(CommonFields{}, hash, eq, alloc) { : settings_(CommonFields{}, hash, eq, alloc) {
if (bucket_count) { if (bucket_count) {
common().capacity_ = NormalizeCapacity(bucket_count); common().set_capacity(NormalizeCapacity(bucket_count));
initialize_slots(); initialize_slots();
} }
} }
...@@ -1767,8 +1782,8 @@ class raw_hash_set { ...@@ -1767,8 +1782,8 @@ class raw_hash_set {
common().maybe_increment_generation_on_insert(); common().maybe_increment_generation_on_insert();
infoz().RecordInsert(hash, target.probe_length); infoz().RecordInsert(hash, target.probe_length);
} }
common().size_ = that.size(); common().set_size(that.size());
growth_left() -= that.size(); set_growth_left(growth_left() - that.size());
} }
ABSL_ATTRIBUTE_NOINLINE raw_hash_set(raw_hash_set&& that) noexcept( ABSL_ATTRIBUTE_NOINLINE raw_hash_set(raw_hash_set&& that) noexcept(
...@@ -1849,8 +1864,8 @@ class raw_hash_set { ...@@ -1849,8 +1864,8 @@ class raw_hash_set {
const_iterator cend() const ABSL_ATTRIBUTE_LIFETIME_BOUND { return end(); } const_iterator cend() const ABSL_ATTRIBUTE_LIFETIME_BOUND { return end(); }
bool empty() const { return !size(); } bool empty() const { return !size(); }
size_t size() const { return common().size_; } size_t size() const { return common().size(); }
size_t capacity() const { return common().capacity_; } size_t capacity() const { return common().capacity(); }
size_t max_size() const { return (std::numeric_limits<size_t>::max)(); } size_t max_size() const { return (std::numeric_limits<size_t>::max)(); }
ABSL_ATTRIBUTE_REINITIALIZES void clear() { ABSL_ATTRIBUTE_REINITIALIZES void clear() {
...@@ -2432,8 +2447,8 @@ class raw_hash_set { ...@@ -2432,8 +2447,8 @@ class raw_hash_set {
assert(IsValidCapacity(new_capacity)); assert(IsValidCapacity(new_capacity));
auto* old_ctrl = control(); auto* old_ctrl = control();
auto* old_slots = slot_array(); auto* old_slots = slot_array();
const size_t old_capacity = common().capacity_; const size_t old_capacity = common().capacity();
common().capacity_ = new_capacity; common().set_capacity(new_capacity);
initialize_slots(); initialize_slots();
auto* new_slots = slot_array(); auto* new_slots = slot_array();
...@@ -2600,8 +2615,8 @@ class raw_hash_set { ...@@ -2600,8 +2615,8 @@ class raw_hash_set {
rehash_and_grow_if_necessary(); rehash_and_grow_if_necessary();
target = find_first_non_full(common(), hash); target = find_first_non_full(common(), hash);
} }
++common().size_; common().set_size(common().size() + 1);
growth_left() -= IsEmpty(control()[target.offset]); set_growth_left(growth_left() - IsEmpty(control()[target.offset]));
SetCtrl(common(), target.offset, H2(hash), sizeof(slot_type)); SetCtrl(common(), target.offset, H2(hash), sizeof(slot_type));
common().maybe_increment_generation_on_insert(); common().maybe_increment_generation_on_insert();
infoz().RecordInsert(hash, target.probe_length); infoz().RecordInsert(hash, target.probe_length);
...@@ -2646,7 +2661,8 @@ class raw_hash_set { ...@@ -2646,7 +2661,8 @@ class raw_hash_set {
// side-effect. // side-effect.
// //
// See `CapacityToGrowth()`. // See `CapacityToGrowth()`.
size_t& growth_left() { return common().growth_left(); } size_t growth_left() const { return common().growth_left(); }
void set_growth_left(size_t gl) { return common().set_growth_left(gl); }
// Prefetch the heap-allocated memory region to resolve potential TLB and // Prefetch the heap-allocated memory region to resolve potential TLB and
// cache misses. This is intended to overlap with execution of calculating the // cache misses. This is intended to overlap with execution of calculating the
...@@ -2660,9 +2676,9 @@ class raw_hash_set { ...@@ -2660,9 +2676,9 @@ class raw_hash_set {
CommonFields& common() { return settings_.template get<0>(); } CommonFields& common() { return settings_.template get<0>(); }
const CommonFields& common() const { return settings_.template get<0>(); } const CommonFields& common() const { return settings_.template get<0>(); }
ctrl_t* control() const { return common().control_; } ctrl_t* control() const { return common().control(); }
slot_type* slot_array() const { slot_type* slot_array() const {
return static_cast<slot_type*>(common().slots_); return static_cast<slot_type*>(common().slots());
} }
HashtablezInfoHandle& infoz() { return common().infoz(); } HashtablezInfoHandle& infoz() { return common().infoz(); }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment