xo-arena: + DArenaHashMap::operator[] + utest

This commit is contained in:
Roland Conybeare 2026-01-15 19:36:31 -05:00
commit 3711ab21a4
3 changed files with 96 additions and 7 deletions

View file

@ -98,8 +98,8 @@ namespace xo {
* Replaces any previous value stored under the same key.
*
* Return pair retval with:
* reval.first: true if size incremented;
* retval.second: address of slots_[p] at which pair inserted/updated
* retval.first: address of slots_[p] at which pair inserted/updated
* retval.second: true if size incremented;
*
* When table is full retval.second will be nullptr,
* with error captured in last_error_
@ -119,6 +119,9 @@ namespace xo {
**/
iterator find(const key_type & key);
/** establish kv pair for @p key in this table; return address of value part **/
mapped_type & operator[](const key_type & key);
private:
/** insert @p kv_pair,
* where key hashes to @p hash_value, into @p *store
@ -169,7 +172,7 @@ namespace xo {
bool debug_flag)
: hash_{std::move(hash)},
equal_{std::move(eq)},
store_{lub_exp2(lub_group_mult(hint_max_capacity))},
store_{"arenahashmap", lub_exp2(lub_group_mult(hint_max_capacity))},
debug_flag_{debug_flag}
{
}
@ -332,7 +335,8 @@ namespace xo {
} else {
log && log("duplicate-and-replace branch");
detail::HashMapStore<Key, Value> store_2x(std::make_pair(n_group_exponent_2x,
detail::HashMapStore<Key, Value> store_2x("arenahashmap",
std::make_pair(n_group_exponent_2x,
n_group_2x));
/* rehash everything in store_,
* into store_2x
@ -459,6 +463,42 @@ namespace xo {
}
}
template <typename Key,
typename Value,
typename Hash,
typename Equal>
auto
DArenaHashMap<Key, Value, Hash, Equal>::operator[](const key_type & key) -> mapped_type &
{
{
auto ix = this->find(key);
if (ix != this->end())
return ix->second;
}
// key-value pair
value_type kv_pair = std::make_pair(key, mapped_type{});
auto [slot_addr, ins_flag] = this->try_insert(kv_pair);
if (slot_addr)
return slot_addr->second;
if (!this->_try_grow()) {
// we are out of room
throw std::runtime_error("DArenaHashMap::operator[]: table capacity exhausted");
}
/* retry insert, now with bigger capacity */
std::tie(slot_addr, ins_flag) = this->try_insert(kv_pair);
assert(slot_addr);
return slot_addr->second;
}
/**
* Verify DArenaHashMap class invariants.
*

View file

@ -22,14 +22,15 @@ namespace xo {
public:
/** group_exp2: number of groups {x, 2^x} **/
explicit HashMapStore(const std::pair<size_type,
explicit HashMapStore(const std::string & name,
const std::pair<size_type,
size_type> & group_exp2)
: size_{0},
n_group_exponent_{group_exp2.first},
n_group_{group_exp2.second},
n_slot_{group_exp2.second * c_group_size},
control_{control_vector_type::map(xo::mm::ArenaConfig{.size_ = control_size(n_slot_)})},
slots_{slot_vector_type::map(xo::mm::ArenaConfig{.size_ = n_slot_ * sizeof(value_type)})}
control_{control_vector_type::map(xo::mm::ArenaConfig{.name_ = name, .size_ = control_size(n_slot_)})},
slots_{slot_vector_type::map(xo::mm::ArenaConfig{.name_ = name, .size_ = n_slot_ * sizeof(value_type)})}
{
/* here: arenas have allocated address range, but no committed memory yet */

View file

@ -226,6 +226,54 @@ namespace xo {
}
}
TEST_CASE("DArenaHashMap-operator-bracket", "[arena][DArenaHashMap]")
{
using HashMap = DArenaHashMap<int, int>;
HashMap map;
// insert via operator[]
map[1] = 100;
map[2] = 200;
map[3] = 300;
REQUIRE(map.size() == 3);
// read back via operator[]
REQUIRE(map[1] == 100);
REQUIRE(map[2] == 200);
REQUIRE(map[3] == 300);
// update via operator[]
map[2] = 250;
REQUIRE(map[2] == 250);
REQUIRE(map.size() == 3); // size unchanged
// verify via find
{
auto it = map.find(1);
REQUIRE(it != map.end());
REQUIRE(it->second == 100);
}
{
auto it = map.find(2);
REQUIRE(it != map.end());
REQUIRE(it->second == 250);
}
{
auto it = map.find(3);
REQUIRE(it != map.end());
REQUIRE(it->second == 300);
}
// operator[] on non-existent key creates default entry
int & val = map[999];
REQUIRE(map.size() == 4);
REQUIRE(val == 0); // default-initialized
val = 999;
REQUIRE(map[999] == 999);
}
// TODO:
// - let's try getting lcov to work in xo-umbrella2
}