Add support for temporary spans in additional absl::c_* container algorithms. Allow `absl::c_partial_sort`, `absl::c_lower_bound`, `absl::c_upper_bound`, `absl::c_equal_range`, `absl::c_sort_heap`, `absl::c_min_element`, and `absl::c_max_element` to accept rvalue spans while continuing to reject other temporary containers via `IsBorrowedRange`. Renamed `IsPermissibleDestinationRange` to `IsBorrowedRange`. PiperOrigin-RevId: 998254614
diff --git a/absl/algorithm/container.h b/absl/algorithm/container.h index 5c2d376..1db7da7 100644 --- a/absl/algorithm/container.h +++ b/absl/algorithm/container.h
@@ -209,29 +209,32 @@ struct IsSpan<std::span<T, Extent>> : std::true_type {}; #endif -// Indicates whether the given type is safe to pass as a sink to a function such -// as absl::c_fill(). Similar idea as std::ranges::borrowed_range. +// Indicates whether the given type is a borrowed range, meaning it is safe to +// return iterators from it even when passed as an rvalue (or use as a sink +// for algorithms that mutate the container). Similar to +// std::ranges::borrowed_range. // // We are deliberately conservative here and only support lvalues and spans for // now, in order to avoid divergence from C++17 or potentially unforeseen // consequences. If needed in the future, we can probably extend this to all // types that satisfy std::ranges::borrowed_range. template <typename C> -using IsPermissibleDestinationRange = - std::conditional_t<std::is_lvalue_reference_v<C>, std::true_type, - IsSpan<C>>; +using IsBorrowedRange = std::conditional_t<std::is_lvalue_reference_v<C>, + std::true_type, IsSpan<C>>; + +template <typename C> +using IsPermissibleDestinationRange = IsBorrowedRange<C>; template <typename C, typename OutputRange> -using ResultOfRangeToRangeTransfer = - std::enable_if_t<container_algorithm_internal::HasBeginEnd< - std::add_lvalue_reference_t<OutputRange>>::value && - !container_algorithm_internal::IsMultidimensionalArray< - std::remove_reference_t<OutputRange>>::value && - !container_algorithm_internal::IsMultidimensionalArray< - std::remove_reference_t<C>>::value && - container_algorithm_internal:: - IsPermissibleDestinationRange<OutputRange>::value, - void>; +using ResultOfRangeToRangeTransfer = std::enable_if_t< + container_algorithm_internal::HasBeginEnd< + std::add_lvalue_reference_t<OutputRange>>::value && + !container_algorithm_internal::IsMultidimensionalArray< + std::remove_reference_t<OutputRange>>::value && + !container_algorithm_internal::IsMultidimensionalArray< + std::remove_reference_t<C>>::value && + container_algorithm_internal::IsBorrowedRange<OutputRange>::value, + void>; } // namespace container_algorithm_internal @@ -919,7 +922,7 @@ // container with some value. template <typename C, typename T> constexpr std::enable_if_t< - container_algorithm_internal::IsPermissibleDestinationRange<C>::value, void> + container_algorithm_internal::IsBorrowedRange<C>::value, void> c_fill(C&& c, const T& value) { std::fill(container_algorithm_internal::c_begin(c), container_algorithm_internal::c_end(c), value); @@ -931,7 +934,7 @@ // the first N elements in a container with some value. template <typename C, typename Size, typename T> constexpr std::enable_if_t< - container_algorithm_internal::IsPermissibleDestinationRange<C>::value, void> + container_algorithm_internal::IsBorrowedRange<C>::value, void> c_fill_n(C&& c, Size n, const T& value) { std::fill_n(container_algorithm_internal::c_begin(c), n, value); } @@ -1170,7 +1173,7 @@ // to sort elements in ascending order of their values. template <typename C> constexpr std::enable_if_t< - container_algorithm_internal::IsPermissibleDestinationRange<C>::value, void> + container_algorithm_internal::IsBorrowedRange<C>::value, void> c_sort(C&& c) { std::sort(container_algorithm_internal::c_begin(c), container_algorithm_internal::c_end(c)); @@ -1180,7 +1183,7 @@ // default `operator<`. template <typename C, typename LessThan> constexpr std::enable_if_t< - container_algorithm_internal::IsPermissibleDestinationRange<C>::value, void> + container_algorithm_internal::IsBorrowedRange<C>::value, void> c_sort(C&& c, LessThan&& comp) { std::sort(container_algorithm_internal::c_begin(c), container_algorithm_internal::c_end(c), @@ -1193,8 +1196,7 @@ // to sort elements in ascending order of their values, preserving the order // of equivalents. template <typename C> -std::enable_if_t< - container_algorithm_internal::IsPermissibleDestinationRange<C>::value, void> +std::enable_if_t<container_algorithm_internal::IsBorrowedRange<C>::value, void> c_stable_sort(C&& c) { std::stable_sort(container_algorithm_internal::c_begin(c), container_algorithm_internal::c_end(c)); @@ -1203,8 +1205,7 @@ // Overload of c_stable_sort() for performing a `comp` comparison other than the // default `operator<`. template <typename C, typename LessThan> -std::enable_if_t< - container_algorithm_internal::IsPermissibleDestinationRange<C>::value, void> +std::enable_if_t<container_algorithm_internal::IsBorrowedRange<C>::value, void> c_stable_sort(C&& c, LessThan&& comp) { std::stable_sort(container_algorithm_internal::c_begin(c), container_algorithm_internal::c_end(c), @@ -1236,8 +1237,11 @@ // to rearrange elements within a container such that elements before `middle` // are sorted in ascending order. template <typename RandomAccessContainer> -constexpr void c_partial_sort( - RandomAccessContainer& sequence, +constexpr std::enable_if_t< + container_algorithm_internal::IsBorrowedRange<RandomAccessContainer>::value, + void> +c_partial_sort( + RandomAccessContainer&& sequence, container_algorithm_internal::ContainerIter<RandomAccessContainer> middle) { std::partial_sort(container_algorithm_internal::c_begin(sequence), middle, container_algorithm_internal::c_end(sequence)); @@ -1246,8 +1250,11 @@ // Overload of c_partial_sort() for performing a `comp` comparison other than // the default `operator<`. template <typename RandomAccessContainer, typename LessThan> -constexpr void c_partial_sort( - RandomAccessContainer& sequence, +constexpr std::enable_if_t< + container_algorithm_internal::IsBorrowedRange<RandomAccessContainer>::value, + void> +c_partial_sort( + RandomAccessContainer&& sequence, container_algorithm_internal::ContainerIter<RandomAccessContainer> middle, LessThan&& comp) { std::partial_sort(container_algorithm_internal::c_begin(sequence), middle, @@ -1343,8 +1350,10 @@ // to return an iterator pointing to the first element in a sorted container // which does not compare less than `value`. template <typename Sequence, typename T> -constexpr container_algorithm_internal::ContainerIter<Sequence> c_lower_bound( - Sequence& sequence, const T& value) { +constexpr std::enable_if_t< + container_algorithm_internal::IsBorrowedRange<Sequence>::value, + container_algorithm_internal::ContainerIter<Sequence>> +c_lower_bound(Sequence&& sequence, const T& value) { return std::lower_bound(container_algorithm_internal::c_begin(sequence), container_algorithm_internal::c_end(sequence), value); } @@ -1352,8 +1361,10 @@ // Overload of c_lower_bound() for performing a `comp` comparison other than // the default `operator<`. template <typename Sequence, typename T, typename LessThan> -constexpr container_algorithm_internal::ContainerIter<Sequence> c_lower_bound( - Sequence& sequence, const T& value, LessThan&& comp) { +constexpr std::enable_if_t< + container_algorithm_internal::IsBorrowedRange<Sequence>::value, + container_algorithm_internal::ContainerIter<Sequence>> +c_lower_bound(Sequence&& sequence, const T& value, LessThan&& comp) { return std::lower_bound(container_algorithm_internal::c_begin(sequence), container_algorithm_internal::c_end(sequence), value, std::forward<LessThan>(comp)); @@ -1365,8 +1376,10 @@ // to return an iterator pointing to the first element in a sorted container // which is greater than `value`. template <typename Sequence, typename T> -constexpr container_algorithm_internal::ContainerIter<Sequence> c_upper_bound( - Sequence& sequence, const T& value) { +constexpr std::enable_if_t< + container_algorithm_internal::IsBorrowedRange<Sequence>::value, + container_algorithm_internal::ContainerIter<Sequence>> +c_upper_bound(Sequence&& sequence, const T& value) { return std::upper_bound(container_algorithm_internal::c_begin(sequence), container_algorithm_internal::c_end(sequence), value); } @@ -1374,8 +1387,10 @@ // Overload of c_upper_bound() for performing a `comp` comparison other than // the default `operator<`. template <typename Sequence, typename T, typename LessThan> -constexpr container_algorithm_internal::ContainerIter<Sequence> c_upper_bound( - Sequence& sequence, const T& value, LessThan&& comp) { +constexpr std::enable_if_t< + container_algorithm_internal::IsBorrowedRange<Sequence>::value, + container_algorithm_internal::ContainerIter<Sequence>> +c_upper_bound(Sequence&& sequence, const T& value, LessThan&& comp) { return std::upper_bound(container_algorithm_internal::c_begin(sequence), container_algorithm_internal::c_end(sequence), value, std::forward<LessThan>(comp)); @@ -1387,9 +1402,10 @@ // to return an iterator pair pointing to the first and last elements in a // sorted container which compare equal to `value`. template <typename Sequence, typename T> -constexpr container_algorithm_internal::ContainerIterPairType<Sequence, - Sequence> -c_equal_range(Sequence& sequence, const T& value) { +constexpr std::enable_if_t< + container_algorithm_internal::IsBorrowedRange<Sequence>::value, + container_algorithm_internal::ContainerIterPairType<Sequence, Sequence>> +c_equal_range(Sequence&& sequence, const T& value) { return std::equal_range(container_algorithm_internal::c_begin(sequence), container_algorithm_internal::c_end(sequence), value); } @@ -1397,9 +1413,10 @@ // Overload of c_equal_range() for performing a `comp` comparison other than // the default `operator<`. template <typename Sequence, typename T, typename LessThan> -constexpr container_algorithm_internal::ContainerIterPairType<Sequence, - Sequence> -c_equal_range(Sequence& sequence, const T& value, LessThan&& comp) { +constexpr std::enable_if_t< + container_algorithm_internal::IsBorrowedRange<Sequence>::value, + container_algorithm_internal::ContainerIterPairType<Sequence, Sequence>> +c_equal_range(Sequence&& sequence, const T& value, LessThan&& comp) { return std::equal_range(container_algorithm_internal::c_begin(sequence), container_algorithm_internal::c_end(sequence), value, std::forward<LessThan>(comp)); @@ -1726,7 +1743,10 @@ // Container-based version of the <algorithm> `std::sort_heap()` function // to sort a heap into ascending order (after which it is no longer a heap). template <typename RandomAccessContainer> -constexpr void c_sort_heap(RandomAccessContainer& sequence) { +constexpr std::enable_if_t< + container_algorithm_internal::IsBorrowedRange<RandomAccessContainer>::value, + void> +c_sort_heap(RandomAccessContainer&& sequence) { std::sort_heap(container_algorithm_internal::c_begin(sequence), container_algorithm_internal::c_end(sequence)); } @@ -1734,7 +1754,10 @@ // Overload of c_sort_heap() for performing heap comparisons using a // `comp` other than `operator<` template <typename RandomAccessContainer, typename LessThan> -constexpr void c_sort_heap(RandomAccessContainer& sequence, LessThan&& comp) { +constexpr std::enable_if_t< + container_algorithm_internal::IsBorrowedRange<RandomAccessContainer>::value, + void> +c_sort_heap(RandomAccessContainer&& sequence, LessThan&& comp) { std::sort_heap(container_algorithm_internal::c_begin(sequence), container_algorithm_internal::c_end(sequence), std::forward<LessThan>(comp)); @@ -1791,8 +1814,10 @@ // to return an iterator pointing to the element with the smallest value, using // `operator<` to make the comparisons. template <typename Sequence> -constexpr container_algorithm_internal::ContainerIter<Sequence> c_min_element( - Sequence& sequence) { +constexpr std::enable_if_t< + container_algorithm_internal::IsBorrowedRange<Sequence>::value, + container_algorithm_internal::ContainerIter<Sequence>> +c_min_element(Sequence&& sequence) { return std::min_element(container_algorithm_internal::c_begin(sequence), container_algorithm_internal::c_end(sequence)); } @@ -1800,8 +1825,10 @@ // Overload of c_min_element() for performing a `comp` comparison other than // `operator<`. template <typename Sequence, typename LessThan> -constexpr container_algorithm_internal::ContainerIter<Sequence> c_min_element( - Sequence& sequence, LessThan&& comp) { +constexpr std::enable_if_t< + container_algorithm_internal::IsBorrowedRange<Sequence>::value, + container_algorithm_internal::ContainerIter<Sequence>> +c_min_element(Sequence&& sequence, LessThan&& comp) { return std::min_element(container_algorithm_internal::c_begin(sequence), container_algorithm_internal::c_end(sequence), std::forward<LessThan>(comp)); @@ -1813,8 +1840,10 @@ // to return an iterator pointing to the element with the largest value, using // `operator<` to make the comparisons. template <typename Sequence> -constexpr container_algorithm_internal::ContainerIter<Sequence> c_max_element( - Sequence& sequence) { +constexpr std::enable_if_t< + container_algorithm_internal::IsBorrowedRange<Sequence>::value, + container_algorithm_internal::ContainerIter<Sequence>> +c_max_element(Sequence&& sequence) { return std::max_element(container_algorithm_internal::c_begin(sequence), container_algorithm_internal::c_end(sequence)); } @@ -1822,8 +1851,10 @@ // Overload of c_max_element() for performing a `comp` comparison other than // `operator<`. template <typename Sequence, typename LessThan> -constexpr container_algorithm_internal::ContainerIter<Sequence> c_max_element( - Sequence& sequence, LessThan&& comp) { +constexpr std::enable_if_t< + container_algorithm_internal::IsBorrowedRange<Sequence>::value, + container_algorithm_internal::ContainerIter<Sequence>> +c_max_element(Sequence&& sequence, LessThan&& comp) { return std::max_element(container_algorithm_internal::c_begin(sequence), container_algorithm_internal::c_end(sequence), std::forward<LessThan>(comp));
diff --git a/absl/algorithm/container_test.cc b/absl/algorithm/container_test.cc index 50a621e..dda791b 100644 --- a/absl/algorithm/container_test.cc +++ b/absl/algorithm/container_test.cc
@@ -371,6 +371,12 @@ EXPECT_EQ(3, *i); } +TEST_F(NonMutatingTest, LowerBoundTemporarySpan) { + int* i = absl::c_lower_bound(absl::MakeSpan(vector_), 3); + EXPECT_EQ(2, std::distance(vector_.data(), i)); + EXPECT_EQ(3, *i); +} + TEST_F(NonMutatingTest, LowerBoundWithPredicate) { std::vector<int> v(vector_); std::sort(v.begin(), v.end(), std::greater<int>()); @@ -379,6 +385,14 @@ EXPECT_EQ(3, *i); } +TEST_F(NonMutatingTest, LowerBoundTemporarySpanWithPredicate) { + std::vector<int> v(vector_); + std::sort(v.begin(), v.end(), std::greater<int>()); + int* i = absl::c_lower_bound(absl::MakeSpan(v), 3, std::greater<int>()); + EXPECT_TRUE(i == v.data()); + EXPECT_EQ(3, *i); +} + TEST_F(NonMutatingTest, UpperBound) { std::list<int>::iterator i = absl::c_upper_bound(sequence_, 1); ASSERT_TRUE(i != sequence_.end()); @@ -386,6 +400,12 @@ EXPECT_EQ(2, *i); } +TEST_F(NonMutatingTest, UpperBoundTemporarySpan) { + int* i = absl::c_upper_bound(absl::MakeSpan(vector_), 1); + EXPECT_EQ(1, std::distance(vector_.data(), i)); + EXPECT_EQ(2, *i); +} + TEST_F(NonMutatingTest, UpperBoundWithPredicate) { std::vector<int> v(vector_); std::sort(v.begin(), v.end(), std::greater<int>()); @@ -394,6 +414,14 @@ EXPECT_TRUE(i == v.end()); } +TEST_F(NonMutatingTest, UpperBoundTemporarySpanWithPredicate) { + std::vector<int> v(vector_); + std::sort(v.begin(), v.end(), std::greater<int>()); + int* i = absl::c_upper_bound(absl::MakeSpan(v), 1, std::greater<int>()); + EXPECT_EQ(3, i - v.data()); + EXPECT_TRUE(i == v.data() + v.size()); +} + TEST_F(NonMutatingTest, EqualRange) { std::pair<std::list<int>::iterator, std::list<int>::iterator> p = absl::c_equal_range(sequence_, 2); @@ -401,6 +429,12 @@ EXPECT_EQ(2, std::distance(sequence_.begin(), p.second)); } +TEST_F(NonMutatingTest, EqualRangeTemporarySpan) { + std::pair<int*, int*> p = absl::c_equal_range(absl::MakeSpan(vector_), 2); + EXPECT_EQ(1, std::distance(vector_.data(), p.first)); + EXPECT_EQ(2, std::distance(vector_.data(), p.second)); +} + TEST_F(NonMutatingTest, EqualRangeArray) { auto p = absl::c_equal_range(array_, 2); EXPECT_EQ(1, std::distance(std::begin(array_), p.first)); @@ -416,6 +450,15 @@ EXPECT_EQ(2, std::distance(v.begin(), p.second)); } +TEST_F(NonMutatingTest, EqualRangeTemporarySpanWithPredicate) { + std::vector<int> v(vector_); + std::sort(v.begin(), v.end(), std::greater<int>()); + std::pair<int*, int*> p = + absl::c_equal_range(absl::MakeSpan(v), 2, std::greater<int>()); + EXPECT_EQ(1, std::distance(v.data(), p.first)); + EXPECT_EQ(2, std::distance(v.data(), p.second)); +} + TEST_F(NonMutatingTest, BinarySearch) { EXPECT_TRUE(absl::c_binary_search(vector_, 2)); EXPECT_TRUE(absl::c_binary_search(std::vector<int>(vector_), 2)); @@ -435,6 +478,12 @@ EXPECT_EQ(*i, 1); } +TEST_F(NonMutatingTest, MinElementTemporarySpan) { + int* i = absl::c_min_element(absl::MakeSpan(vector_)); + ASSERT_TRUE(i != vector_.data() + vector_.size()); + EXPECT_EQ(*i, 1); +} + TEST_F(NonMutatingTest, MinElementWithPredicate) { std::list<int>::iterator i = absl::c_min_element(sequence_, std::greater<int>()); @@ -442,12 +491,24 @@ EXPECT_EQ(*i, 3); } +TEST_F(NonMutatingTest, MinElementTemporarySpanWithPredicate) { + int* i = absl::c_min_element(absl::MakeSpan(vector_), std::greater<int>()); + ASSERT_TRUE(i != vector_.data() + vector_.size()); + EXPECT_EQ(*i, 3); +} + TEST_F(NonMutatingTest, MaxElement) { std::list<int>::iterator i = absl::c_max_element(sequence_); ASSERT_TRUE(i != sequence_.end()); EXPECT_EQ(*i, 3); } +TEST_F(NonMutatingTest, MaxElementTemporarySpan) { + int* i = absl::c_max_element(absl::MakeSpan(vector_)); + ASSERT_TRUE(i != vector_.data() + vector_.size()); + EXPECT_EQ(*i, 3); +} + TEST_F(NonMutatingTest, MaxElementWithPredicate) { std::list<int>::iterator i = absl::c_max_element(sequence_, std::greater<int>()); @@ -455,6 +516,12 @@ EXPECT_EQ(*i, 1); } +TEST_F(NonMutatingTest, MaxElementTemporarySpanWithPredicate) { + int* i = absl::c_max_element(absl::MakeSpan(vector_), std::greater<int>()); + ASSERT_TRUE(i != vector_.data() + vector_.size()); + EXPECT_EQ(*i, 1); +} + TEST_F(NonMutatingTest, LexicographicalCompare) { EXPECT_FALSE(absl::c_lexicographical_compare(sequence_, sequence_)); @@ -1533,6 +1600,15 @@ EXPECT_THAT(absl::MakeSpan(sequence.data(), 2), ElementsAre(42, 5)); } +TEST(MutatingTest, PartialSortTemporarySpan) { + std::vector<int> sequence{5, 3, 42, 0}; + absl::c_partial_sort(absl::MakeSpan(sequence), sequence.data() + 2); + EXPECT_THAT(absl::MakeSpan(sequence).first(2), ElementsAre(0, 3)); + absl::c_partial_sort(absl::MakeSpan(sequence), sequence.data() + 2, + std::greater<int>()); + EXPECT_THAT(absl::MakeSpan(sequence).first(2), ElementsAre(42, 5)); +} + TEST(MutatingTest, PartialSortCopy) { const std::vector<int> initial = {5, 3, 42, 0}; std::vector<int> actual(2); @@ -1664,6 +1740,23 @@ EXPECT_FALSE(absl::c_is_heap(heap, greater())); } +TEST(HeapOperationsTest, SortHeapTemporarySpan) { + std::vector<int> heap = {1, 2, 3, 4}; + absl::c_make_heap(heap); + absl::c_sort_heap(absl::MakeSpan(heap)); + EXPECT_THAT(heap, ElementsAre(1, 2, 3, 4)); + EXPECT_FALSE(absl::c_is_heap(heap)); +} + +TEST(HeapOperationsTest, SortHeapTemporarySpanWithComparator) { + using greater = std::greater<int>; + std::vector<int> heap = {4, 3, 2, 1}; + absl::c_make_heap(heap, greater()); + absl::c_sort_heap(absl::MakeSpan(heap), greater()); + EXPECT_THAT(heap, ElementsAre(4, 3, 2, 1)); + EXPECT_FALSE(absl::c_is_heap(heap, greater())); +} + TEST(MutatingTest, PermutationOperations) { std::vector<int> initial = {1, 2, 3, 4}; std::vector<int> permuted = initial;
diff --git a/absl/container/internal/raw_hash_set.cc b/absl/container/internal/raw_hash_set.cc index baed443..e60b237 100644 --- a/absl/container/internal/raw_hash_set.cc +++ b/absl/container/internal/raw_hash_set.cc
@@ -852,7 +852,7 @@ ABSL_SWISSTABLE_ASSERT(c.capacity() > 0); if (policy.destroy_slot != nullptr) { if (c.is_small()) { - if (!c.empty()) { + if (!c.empty_when_small()) { static_assert(kMaxSmallCapacity == 1); policy.destroy_slot(&c, c.single_non_soo_slot()); } @@ -948,7 +948,7 @@ if (c.is_small()) { // In the small case, we keep the backing array allocation because the // memory overhead is small compared to the potential benefits of reuse. - if (!c.empty()) { + if (!c.empty_when_small()) { if (destroy_slot != nullptr) { destroy_slot(&c, SingleSlotAddress<kSooEnabled>(c)); } @@ -2039,7 +2039,7 @@ void* hasher, HashForKeyFn get_hash) { ABSL_SWISSTABLE_ASSERT(common.is_small()); ABSL_SWISSTABLE_ASSERT(!policy.soo_enabled); - const bool is_empty = common.empty(); + const bool is_empty = common.empty_when_small(); if (common.capacity() == 1 && is_empty) { IncrementSmallSizeNonSoo(common, policy); // Compute before `RecordInsertMissCold` to avoid reloading `control_`. @@ -2052,7 +2052,7 @@ return res; } - const size_t size = common.size(); + const size_t size = is_empty ? 0 : 1; const bool has_infoz = is_empty ? (policy.is_hashtablez_enabled && ShouldSampleNextTable()) : common.has_infoz();
diff --git a/absl/container/internal/raw_hash_set.h b/absl/container/internal/raw_hash_set.h index ce8a1e4..08cc0d1 100644 --- a/absl/container/internal/raw_hash_set.h +++ b/absl/container/internal/raw_hash_set.h
@@ -853,6 +853,12 @@ void decrement_size() { data_ -= kSizeOneNoMetadata; } // Returns true if the table is empty. bool empty() const { return data_ < kSizeOneNoMetadata; } + // Returns true if a small table (capacity <= 1) is empty. + bool empty_when_small() const { + ABSL_SWISSTABLE_ASSERT(is_small()); + ABSL_SWISSTABLE_ASSERT(size() <= 1); + return (data_ & kSizeOneNoMetadata) == 0; + } // Returns true if an empty SOO table has already queried should_sample_soo(). bool soo_has_tried_sampling() const { @@ -1419,6 +1425,7 @@ inline_data_.decrement_size(); } bool empty() const { return inline_data_.empty(); } + bool empty_when_small() const { return inline_data_.empty_when_small(); } void set_soo_has_tried_sampling() { inline_data_.set_soo_has_tried_sampling(); } @@ -2515,18 +2522,15 @@ return SizeToCapacity(MaxValidSize()); } - // Whether `size` fits in the SOO capacity of this table. - bool fits_in_soo(size_t size) const { - return SooEnabled() && size <= SooCapacity(); - } // Whether this table is in SOO mode or non-SOO mode. bool is_soo() const { - HashtableCapacity cap = maybe_invalid_capacity(); - return cap.IsValid() && fits_in_soo(cap.capacity()); + static_assert(SooCapacity() == kMaxSmallCapacity); + return SooEnabled() && maybe_invalid_capacity().is_small(); } - bool is_full_soo() const { return is_soo() && !empty(); } + bool is_full_soo() const { return is_soo() && !empty_when_small(); } bool is_small() const { return common().is_small(); } + bool empty_when_small() const { return common().empty_when_small(); } // Give an early error when key_type is not hashable/eq. auto KeyTypeCanBeHashed(const Hash& h, const key_type& k) -> decltype(h(k)); @@ -3351,7 +3355,7 @@ }; if (src.is_small()) { - if (src.empty()) return; + if (src.empty_when_small()) return; if (insert_slot(src.single_slot())) src.erase_meta_only_small(); return; @@ -3613,7 +3617,9 @@ // SOO functionality. template <class K> ABSL_ATTRIBUTE_ALWAYS_INLINE iterator find_small(const K& key) { - return empty() || !equal_to(key, single_slot()) ? end() : single_iterator(); + return empty_when_small() || !equal_to(key, single_slot()) + ? end() + : single_iterator(); } template <class K> @@ -3687,8 +3693,8 @@ kIsDefaultAlloc && BackingArrayAlignment(alignof(slot_type)) == kStandardBackingArrayAlignment; if constexpr (SooEnabled()) { - if (is_small() && - (PolicyTraits::template destroy_is_trivial<Alloc>() || empty())) { + if (is_small() && (PolicyTraits::template destroy_is_trivial<Alloc>() || + empty_when_small())) { return; } } else { @@ -3850,7 +3856,7 @@ find_or_prepare_insert_soo(const K& key) { bool force_sampling; slot_type* slot = single_slot(); - if (empty()) { + if (empty_when_small()) { if (!should_sample_soo()) { common().set_full_soo(); return {slot, true}; @@ -3880,7 +3886,7 @@ if constexpr (SooEnabled()) { return find_or_prepare_insert_soo(key); } - if (!empty()) { + if (!empty_when_small()) { if (equal_to(key, single_slot())) { return {single_slot(), false}; }