Commit 40070892 by Evan Brown Committed by Copybara-Service

roll forward: Make data members of CommonFields be private so that it's easier…

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

PiperOrigin-RevId: 544732832
Change-Id: I0c9a30f18edc71b3c7fffe94e2002ff6c52050f8
parent 495399de
...@@ -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_ptr();
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_ptr(), 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,35 @@ class CommonFields : public CommonFieldsGenerationInfo { ...@@ -906,21 +911,35 @@ 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_ptr()),
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; }
// Note: we can't use slots() because Qt defines "slots" as a macro.
void* slots_ptr() 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 +948,17 @@ class CommonFields : public CommonFieldsGenerationInfo { ...@@ -929,15 +948,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 +992,6 @@ constexpr size_t NumClonedBytes() { return Group::kWidth - 1; } ...@@ -971,11 +992,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 +1232,7 @@ inline probe_seq<Group::kWidth> probe(const ctrl_t* ctrl, const size_t capacity, ...@@ -1216,7 +1232,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 +1245,7 @@ inline probe_seq<Group::kWidth> probe(const CommonFields& common, size_t hash) { ...@@ -1229,7 +1245,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 +1255,14 @@ inline FindInfo find_first_non_full(const CommonFields& common, size_t hash) { ...@@ -1239,14 +1255,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 +1276,18 @@ extern template FindInfo find_first_non_full(const CommonFields&, size_t); ...@@ -1260,18 +1276,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_ptr(), slot_size * capacity);
ResetGrowthLeft(common); ResetGrowthLeft(common);
} }
...@@ -1281,17 +1297,17 @@ inline void ResetCtrl(CommonFields& common, size_t slot_size) { ...@@ -1281,17 +1297,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_ptr()) + 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 +1343,32 @@ inline size_t AllocSize(size_t capacity, size_t slot_size, size_t slot_align) { ...@@ -1327,31 +1343,32 @@ 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_ptr() == 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 +1671,7 @@ class raw_hash_set { ...@@ -1654,7 +1671,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 +1784,8 @@ class raw_hash_set { ...@@ -1767,8 +1784,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 +1866,8 @@ class raw_hash_set { ...@@ -1849,8 +1866,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 +2449,8 @@ class raw_hash_set { ...@@ -2432,8 +2449,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 +2617,8 @@ class raw_hash_set { ...@@ -2600,8 +2617,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 +2663,8 @@ class raw_hash_set { ...@@ -2646,7 +2663,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 +2678,9 @@ class raw_hash_set { ...@@ -2660,9 +2678,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_ptr());
} }
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