Skip to content

Commit

Permalink
Add a key-returning callback insert to Set. (#4072)
Browse files Browse the repository at this point in the history
This turns out to be super useful now that we have the `key_context`
mechanism and can do much more meaningful heterogeneous lookups, where
the stored key can be *very* different from the lookup key.

---------

Co-authored-by: Richard Smith <[email protected]>
  • Loading branch information
chandlerc and zygoloid authored Jun 22, 2024
1 parent b70cfd0 commit 9d95c68
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 2 deletions.
32 changes: 32 additions & 0 deletions common/set.h
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,21 @@ class SetBase
auto Insert(LookupKeyT lookup_key, KeyContextT key_context = KeyContextT())
-> InsertResult;

// Insert a key into the map and call the provided callback if necessary to
// produce a new key when no existing value is found.
//
// Example: `m.Insert(key_equivalent, [] { return real_key; });`
//
// The point of this function is when the lookup key is _different_from the
// stored key. However, we don't restrict it in case that blocks generic
// usage.
template <typename LookupKeyT, typename KeyCallbackT>
auto Insert(LookupKeyT lookup_key, KeyCallbackT key_cb,
KeyContextT key_context = KeyContextT()) -> InsertResult
requires(
!std::same_as<KeyT, KeyCallbackT> &&
std::convertible_to<decltype(std::declval<KeyCallbackT>()()), KeyT>);

// Insert a key into the set and call the provided callback to allow in-place
// construction of the key if not already present. The lookup key is passed
// through to the callback so it needn't be captured and can be kept in a
Expand Down Expand Up @@ -331,6 +346,23 @@ auto SetBase<InputKeyT, InputKeyContextT>::Insert(LookupKeyT lookup_key,
key_context);
}

template <typename InputKeyT, typename InputKeyContextT>
template <typename LookupKeyT, typename KeyCallbackT>
auto SetBase<InputKeyT, InputKeyContextT>::Insert(LookupKeyT lookup_key,
KeyCallbackT key_cb,
KeyContextT key_context)
-> InsertResult
requires(!std::same_as<KeyT, KeyCallbackT> &&
std::convertible_to<decltype(std::declval<KeyCallbackT>()()), KeyT>)
{
return Insert(
lookup_key,
[&key_cb](LookupKeyT /*lookup_key*/, void* key_storage) {
new (key_storage) KeyT(key_cb());
},
key_context);
}

template <typename InputKeyT, typename InputKeyContextT>
template <typename LookupKeyT, typename InsertCallbackT>
auto SetBase<InputKeyT, InputKeyContextT>::Insert(LookupKeyT lookup_key,
Expand Down
11 changes: 9 additions & 2 deletions common/set_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -343,12 +343,17 @@ TEST(SetContextTest, Basic) {
auto i_result = s.Insert(1, IndexKeyContext<TestData>(keys));
EXPECT_FALSE(i_result.is_inserted());
EXPECT_TRUE(s.Contains(1, key_context));
EXPECT_TRUE(s.Insert(
TestData(200), [] { return 2; }, key_context)
.is_inserted());
EXPECT_TRUE(s.Contains(2, key_context));
EXPECT_TRUE(s.Contains(TestData(200), key_context));

// Verify all the elements.
ExpectSetElementsAre(s, {1});
ExpectSetElementsAre(s, {1, 2});

// Fill up a bunch to ensure we trigger growth a few times.
for (int i : llvm::seq(2, 512)) {
for (int i : llvm::seq(3, 512)) {
SCOPED_TRACE(llvm::formatv("Key: {0}", i).str());
EXPECT_TRUE(s.Insert(i, key_context).is_inserted());
}
Expand All @@ -359,6 +364,8 @@ TEST(SetContextTest, Basic) {
}
EXPECT_FALSE(s.Contains(0, key_context));
EXPECT_FALSE(s.Contains(512, key_context));
EXPECT_FALSE(s.Contains(TestData(0), key_context));
EXPECT_FALSE(s.Contains(TestData(51200), key_context));

// Verify all the elements.
ExpectSetElementsAre(s, MakeElements(llvm::seq(1, 512)));
Expand Down

0 comments on commit 9d95c68

Please sign in to comment.