Move `FieldMap` from a private member class of `FieldHandlerMap` to a public standalone class called `SmallIntMap`. Its design is not specific to field handlers, and it does not actually depend on the `Context...` template parameters of `FieldHandlerMap`. Generalize it a bit by parameterizing it over: * key type (`int` in `FieldHandlerMap`) * expected minimum key (1 in `FieldHandlerMap`) * maximum array capacity (128 in `FieldHandlerMap`) * source construct it from (`absl::flat_hash_map<int, Value>` in `FieldHandlerMap`) `SmallIntMap` is a map optimized for keys being small integers. It supports only lookups, but no incremental building nor iteration. It stores a part of the map covering some range of keys starting from `expected_min_key` in an array. At least `array_capacity` possible keys starting from `expected_min_key` are suitable for array lookup. If all present keys are suitable, the stored array can be smaller than `array_capacity`, covering the range to the largest key. If the map is large, the stored array can be larger than `array_capacity`, as long as it is at least 25% full. Optimizations to `SmallIntMap`: * Move `large_map_` behind a pointer to reduce memory usage in the common case. * Delay constructing elements of `small_values_` to avoid requiring `Value` to be default-constructible, and to make the code initializing them smaller. * Move the slow path of `Find()` to a separate function to inline less code. * Add `RIEGELI_ASSUME(_ == nullptr)` to avoid generating deletion code for an initial assignment to a `std::unique_ptr`. PiperOrigin-RevId: 879470235
diff --git a/riegeli/base/BUILD b/riegeli/base/BUILD index a33a175..3e175b9 100644 --- a/riegeli/base/BUILD +++ b/riegeli/base/BUILD
@@ -650,6 +650,18 @@ ) cc_library( + name = "small_int_map", + hdrs = ["small_int_map.h"], + deps = [ + ":arithmetic", + ":assert", + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/base:nullability", + "@com_google_absl//absl/container:flat_hash_map", + ], +) + +cc_library( name = "binary_search", hdrs = ["binary_search.h"], deps = [
diff --git a/riegeli/base/iterable.h b/riegeli/base/iterable.h index b1eae2c..a57dab8 100644 --- a/riegeli/base/iterable.h +++ b/riegeli/base/iterable.h
@@ -63,9 +63,9 @@ struct IsIterableOf : std::false_type {}; template <typename Iterable, typename Element> -struct IsIterableOf< - Iterable, Element, - std::enable_if_t<std::is_convertible_v<ElementTypeT<Iterable>, Element>>> +struct IsIterableOf<Iterable, Element, + std::enable_if_t<std::is_convertible_v< + ElementTypeT<Iterable>, const Element&>>> : std::true_type {}; // `IsIterableOfPairsWithAssignableValues<Iterable, Key, Value>::value`
diff --git a/riegeli/base/small_int_map.h b/riegeli/base/small_int_map.h new file mode 100644 index 0000000..7ded791 --- /dev/null +++ b/riegeli/base/small_int_map.h
@@ -0,0 +1,301 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef RIEGELI_BASE_SMALL_INT_MAP_H_ +#define RIEGELI_BASE_SMALL_INT_MAP_H_ + +#include <stddef.h> +#include <stdint.h> + +#include <memory> +#include <type_traits> +#include <utility> + +#include "absl/base/attributes.h" +#include "absl/base/nullability.h" +#include "absl/base/optimization.h" +#include "absl/container/flat_hash_map.h" +#include "riegeli/base/arithmetic.h" +#include "riegeli/base/assert.h" + +ABSL_POINTERS_DEFAULT_NONNULL + +namespace riegeli { + +namespace small_int_map_internal { + +template <typename T> +class DelayedConstructor; + +} // namespace small_int_map_internal + +// `SmallIntMap` is a map optimized for keys being small integers. It supports +// only lookups, but no incremental building nor iteration. +// +// It stores a part of the map covering some range of keys starting from +// `expected_min_key` in an array. +// +// At least `array_capacity` possible keys starting from `expected_min_key` are +// suitable for array lookup. If all present keys are suitable, the stored array +// can be smaller than `array_capacity`, covering the range to the largest key. +// If the map is large, the stored array can be larger than `array_capacity`, +// as long as it is at least 25% full. +template <typename Key, typename Value, Key expected_min_key = 0, + size_t array_capacity = 128> +class SmallIntMap { + public: + SmallIntMap() = default; + + // Builds `SmallIntMap` from a pair of iterators over pairs of key and value + // with no duplicate keys. + template <typename Iter> + explicit SmallIntMap(Iter first, Iter last, size_t size) { + RIEGELI_ASSERT_EQ(size, std::distance(first, last)) + << "Failed precondition of SmallIntMap initialization: " + "size does not match the distance between iterators"; + if (size > 0) Optimize(first, last, size); + } + + SmallIntMap(SmallIntMap&& that) noexcept; + SmallIntMap& operator=(SmallIntMap&& that) noexcept; + + // Makes `*this` equivalent to a newly constructed `SmallIntMap`. + ABSL_ATTRIBUTE_REINITIALIZES void Reset(); + template <typename Iter> + ABSL_ATTRIBUTE_REINITIALIZES void Reset(Iter first, Iter last, size_t size) { + RIEGELI_ASSERT_EQ(size, std::distance(first, last)) + << "Failed precondition of SmallIntMap initialization: " + "size does not match the distance between iterators"; + Reset(); + if (size > 0) Optimize(first, last, size); + } + + const Value* absl_nullable Find(Key key) const; + + private: + // The raw key corresponding to a key is the key minus `expected_min_key`, + // represented in an unsigned type with wrap-around. + using RawKey = std::common_type_t<std::make_unsigned_t<Key>, size_t>; + + // A moved-from `SmallIntMap` has `num_small_keys_ == kPoisonedNumSmallKeys` + // and `small_map_ == nullptr`. In debug mode this asserts against using a + // moved-from object. In non-debug mode, if the key is not too large, + // then this triggers a null pointer dereference with an offset up to 1MB, + // which is assumed to reliably crash. + static constexpr size_t kPoisonedNumSmallKeys = + (size_t{1} << 20) / sizeof(const Value*); + + static RawKey ToRawKey(Key key) { + // Wrap-around is not an error. + return static_cast<RawKey>(key) - static_cast<RawKey>(expected_min_key); + } + + template <typename Iter> + void Optimize(Iter first, Iter last, size_t size); + + const Value* absl_nullable FindSlow(Key key) const; + + // The size of `small_map_`, or `kPoisonedNumSmallKeys` for a moved-from + // `SmallIntMap`. + size_t num_small_keys_ = 0; + // Indexed by raw key, with the size of `num_small_keys_`. Elements + // corresponding to present values point to elements of `small_values_`. + // The remaining elements are `nullptr`. + absl_nullable std::unique_ptr<const Value* absl_nullable[]> small_map_; + // Stores values for `small_map_`, in no particular order. + absl_nullable + std::unique_ptr<small_int_map_internal::DelayedConstructor<Value>[]> + small_values_; + // If not `nullptr`, stores the mapping for keys too large for `small_map_`. + // Uses `std::unique_ptr` rather than `std::optional` to reduce memory usage + // in the common case when `large_map_` is not used. + absl_nullable std::unique_ptr<absl::flat_hash_map<Key, Value>> large_map_; +}; + +// Implementation details follow. + +namespace small_int_map_internal { + +template <typename T> +class DelayedConstructor { + public: + // Does not construct the value yet. + DelayedConstructor() noexcept {} + + DelayedConstructor(const DelayedConstructor&) = delete; + DelayedConstructor& operator=(const DelayedConstructor&) = delete; + + ~DelayedConstructor() { value_.~T(); } + + // Construct the value. Must be called exactly once. + template <typename... Args, + std::enable_if_t<std::is_constructible_v<T, Args&&...>, int> = 0> + T& emplace(Args&&... args) ABSL_ATTRIBUTE_LIFETIME_BOUND { + new (&value_) T(std::forward<Args>(args)...); + return value_; + } + + private: + union { + T value_; + }; +}; + +} // namespace small_int_map_internal + +template <typename Key, typename Value, Key expected_min_key, + size_t array_capacity> +SmallIntMap<Key, Value, expected_min_key, array_capacity>::SmallIntMap( + SmallIntMap&& that) noexcept + : num_small_keys_( + std::exchange(that.num_small_keys_, kPoisonedNumSmallKeys)), + small_map_(std::move(that.small_map_)), + small_values_(std::move(that.small_values_)), + large_map_(std::move(that.large_map_)) {} + +template <typename Key, typename Value, Key expected_min_key, + size_t array_capacity> +SmallIntMap<Key, Value, expected_min_key, array_capacity>& +SmallIntMap<Key, Value, expected_min_key, array_capacity>::operator=( + SmallIntMap&& that) noexcept { + num_small_keys_ = std::exchange(that.num_small_keys_, kPoisonedNumSmallKeys); + small_map_ = std::move(that.small_map_); + small_values_ = std::move(that.small_values_); + large_map_ = std::move(that.large_map_); + return *this; +} + +template <typename Key, typename Value, Key expected_min_key, + size_t array_capacity> +void SmallIntMap<Key, Value, expected_min_key, array_capacity>::Reset() { + num_small_keys_ = 0; + small_map_.reset(); + small_values_.reset(); + large_map_.reset(); +} + +template <typename Key, typename Value, Key expected_min_key, + size_t array_capacity> +template <typename Iter> +void SmallIntMap<Key, Value, expected_min_key, array_capacity>::Optimize( + Iter first, Iter last, size_t size) { + RIEGELI_ASSERT_GE(size, 0u) + << "Failed precondition of SmallIntMap::Optimize(): " + "an empty map must have been handled before"; + RawKey max_raw_key = 0; + for (auto iter = first; iter != last; ++iter) { + max_raw_key = UnsignedMax(max_raw_key, ToRawKey(iter->first)); + } + const size_t max_num_small_keys = UnsignedMax(array_capacity, size * 4); + size_t num_small_values; + size_t small_values_index; + if (max_raw_key < max_num_small_keys) { + // All keys are suitable for `small_map_`. `large_map_` is not used. + // + // There is no need for `small_map_` to cover raw keys larger than + // `max_raw_key` because their lookup is fast if `large_map_` is `nullptr`. + num_small_keys_ = IntCast<size_t>(max_raw_key) + 1; + RIEGELI_ASSUME_EQ(small_map_, nullptr) << "Initialization"; + small_map_ = + std::make_unique<const Value* absl_nullable[]>(num_small_keys_); + num_small_values = size; + RIEGELI_ASSUME_EQ(small_values_, nullptr) << "Initialization"; + small_values_ = + std::make_unique<small_int_map_internal::DelayedConstructor<Value>[]>( + num_small_values); + small_values_index = 0; + for (auto iter = first; iter != last; ++iter) { + // `(*iter).second` rather than `iter->second` allows moving from a move + // iterator. + RIEGELI_ASSERT_EQ(small_map_[ToRawKey(iter->first)], nullptr) + << "Failed precondition of SmallIntMap initialization: " + "duplicate key: " + << iter->first; + small_map_[ToRawKey(iter->first)] = + &small_values_[small_values_index++].emplace((*iter).second); + } + } else { + // Some keys are too large for `small_map_`. `large_map_` is used. + // + // `small_map_` covers all raw keys below `max_num_small_keys` rather than + // only up to `max_raw_key`, to reduce lookups in `large_map_`. + num_small_keys_ = max_num_small_keys; + RIEGELI_ASSUME_EQ(small_map_, nullptr) << "Initialization"; + small_map_ = + std::make_unique<const Value* absl_nullable[]>(max_num_small_keys); + num_small_values = 0; + for (auto iter = first; iter != last; ++iter) { + num_small_values += ToRawKey(iter->first) < max_num_small_keys ? 1 : 0; + } + RIEGELI_ASSERT_LT(num_small_values, size) + << "Some keys should have been too large for small_map_"; + RIEGELI_ASSUME_EQ(small_values_, nullptr) << "Initialization"; + if (num_small_values > 0) { + small_values_ = + std::make_unique<small_int_map_internal::DelayedConstructor<Value>[]>( + num_small_values); + } + RIEGELI_ASSUME_EQ(large_map_, nullptr) << "Initialization"; + large_map_ = std::make_unique<absl::flat_hash_map<Key, Value>>(); + large_map_->reserve(size - num_small_values); + small_values_index = 0; + for (auto iter = first; iter != last; ++iter) { + // `(*iter).second` rather than `iter->second` allows moving from a move + // iterator. + if (ToRawKey(iter->first) < max_num_small_keys) { + RIEGELI_ASSERT_EQ(small_map_[ToRawKey(iter->first)], nullptr) + << "Failed precondition of SmallIntMap initialization: " + "duplicate key: " + << iter->first; + small_map_[ToRawKey(iter->first)] = + &small_values_[small_values_index++].emplace((*iter).second); + } else { + const auto inserted = + large_map_->try_emplace(iter->first, (*iter).second); + RIEGELI_ASSERT(inserted.second) + << "Failed precondition of SmallIntMap initialization: " + "duplicate key: " + << iter->first; + } + } + } + RIEGELI_ASSERT_EQ(small_values_index, num_small_values) + << "The whole small_values_ array should have been filled"; +} + +template <typename Key, typename Value, Key expected_min_key, + size_t array_capacity> +ABSL_ATTRIBUTE_ALWAYS_INLINE const Value* absl_nullable +SmallIntMap<Key, Value, expected_min_key, array_capacity>::Find(Key key) const { + RIEGELI_ASSERT(num_small_keys_ != kPoisonedNumSmallKeys || + small_map_ != nullptr) + << "Moved-from SmallIntMap"; + if (ToRawKey(key) < num_small_keys_) return small_map_[ToRawKey(key)]; + if (ABSL_PREDICT_TRUE(large_map_ == nullptr)) return nullptr; + return FindSlow(key); +} + +template <typename Key, typename Value, Key expected_min_key, + size_t array_capacity> +const Value* absl_nullable +SmallIntMap<Key, Value, expected_min_key, array_capacity>::FindSlow( + Key key) const { + const auto iter = large_map_->find(key); + if (iter == large_map_->end()) return nullptr; + return &iter->second; +} + +} // namespace riegeli + +#endif // RIEGELI_BASE_SMALL_INT_MAP_H_
diff --git a/riegeli/messages/BUILD b/riegeli/messages/BUILD index 04248bf..9a2fc4a 100644 --- a/riegeli/messages/BUILD +++ b/riegeli/messages/BUILD
@@ -212,6 +212,7 @@ "//riegeli/base:assert", "//riegeli/base:cord_iterator_span", "//riegeli/base:initializer", + "//riegeli/base:small_int_map", "//riegeli/bytes:limiting_reader", "@com_google_absl//absl/base:core_headers", "@com_google_absl//absl/base:nullability",
diff --git a/riegeli/messages/field_handler_map.h b/riegeli/messages/field_handler_map.h index 7addc41..f8ab6a0 100644 --- a/riegeli/messages/field_handler_map.h +++ b/riegeli/messages/field_handler_map.h
@@ -18,8 +18,6 @@ #include <stddef.h> #include <stdint.h> -#include <memory> -#include <optional> #include <string> #include <type_traits> #include <utility> @@ -35,6 +33,7 @@ #include "riegeli/base/assert.h" #include "riegeli/base/cord_iterator_span.h" #include "riegeli/base/initializer.h" +#include "riegeli/base/small_int_map.h" #include "riegeli/bytes/limiting_reader.h" #include "riegeli/messages/serialized_message_reader.h" #include "riegeli/messages/serialized_message_reader_internal.h" @@ -174,17 +173,12 @@ // For `FieldAction` and `LengthDelimitedActions`. friend class FieldHandlerMapBuilder<Context...>; - // An optimized version of `absl::flat_hash_map<int, Value>` for keys being - // field numbers. - template <typename Value> - class FieldMap; - - FieldMap<FieldAction<uint64_t>> varint_handlers_; - FieldMap<FieldAction<uint32_t>> fixed32_handlers_; - FieldMap<FieldAction<uint64_t>> fixed64_handlers_; - FieldMap<LengthDelimitedActions> length_delimited_handlers_; - FieldMap<FieldAction<>> start_group_handlers_; - FieldMap<FieldAction<>> end_group_handlers_; + SmallIntMap<int, FieldAction<uint64_t>, 1> varint_handlers_; + SmallIntMap<int, FieldAction<uint32_t>, 1> fixed32_handlers_; + SmallIntMap<int, FieldAction<uint64_t>, 1> fixed64_handlers_; + SmallIntMap<int, LengthDelimitedActions, 1> length_delimited_handlers_; + SmallIntMap<int, FieldAction<>, 1> start_group_handlers_; + SmallIntMap<int, FieldAction<>, 1> end_group_handlers_; }; template <typename... Context> @@ -413,181 +407,38 @@ FieldAction<absl::string_view> action_from_string; }; -// An optimized version of `absl::flat_hash_map<int, Value>` for keys being -// field numbers. -// -// Stores an initial portion of the map in an array indexed by field number -// minus 1. This relies on the assumption that typical field numbers are small. -// -// Non-positive field numbers are meaningless, but `FieldMap` works correctly -// with them. -template <typename... Context> -template <typename Value> -class FieldHandlerMap<Context...>::FieldMap { - public: - FieldMap() = default; - - // Builds `FieldMap` from an `absl::flat_hash_map`. - explicit FieldMap(absl::flat_hash_map<int, Value>&& map) { - if (!map.empty()) Optimize(std::move(map)); - } - - FieldMap(FieldMap&& that) noexcept; - FieldMap& operator=(FieldMap&& that) noexcept; - - // Makes `*this` equivalent to a newly constructed `FieldMap`. - ABSL_ATTRIBUTE_REINITIALIZES void Reset(); - ABSL_ATTRIBUTE_REINITIALIZES void Reset( - absl::flat_hash_map<int, Value>&& map) { - Reset(); - if (!map.empty()) Optimize(std::move(map)); - } - - ABSL_ATTRIBUTE_ALWAYS_INLINE - const Value* absl_nullable Find(int field_number) const { - RIEGELI_ASSERT(field_number != kPoisonedNumSmallKeys || - small_map_ != nullptr) - << "Moved-from FieldHandlerMap"; - if (static_cast<size_t>(field_number - 1) < num_small_keys_) { - return small_map_[field_number - 1]; - } - if (ABSL_PREDICT_TRUE(large_map_ == std::nullopt)) return nullptr; - const auto iter = large_map_->find(field_number); - if (iter == large_map_->end()) return nullptr; - return &iter->second; - } - - private: - // A moved-from `FieldMap` has `num_small_keys_ == kPoisonedNumSmallKeys` - // and `small_map_ == nullptr`. In debug mode this asserts against using a - // moved-from object. In non-debug mode, if the field number is not too large, - // then this triggers a null pointer dereference with an offset up to 1MB, - // which is assumed to reliably crash. - static constexpr size_t kPoisonedNumSmallKeys = - (size_t{1} << 20) / sizeof(const Value*); - - void Optimize(absl::flat_hash_map<int, Value>&& map); - - // The size of `small_map_`, or `kPoisonedNumSmallKeys` for a moved-from - // `FieldMap`. - size_t num_small_keys_ = 0; - // Indexed by field number minus 1, where the field number is between 1 and - // `num_small_keys_`. Elements corresponding to registered fields point to - // elements of `small_values_`. The remaining elements are `nullptr`. - absl_nullable std::unique_ptr<const Value* absl_nullable[]> small_map_; - // Stores values for `small_map_`, in no particular order. - absl_nullable std::unique_ptr<Value[]> small_values_; - // If not `std::nullopt`, stores the mapping for field numbers too large for - // `small_map_`. - std::optional<absl::flat_hash_map<int, Value>> large_map_; -}; - -template <typename... Context> -template <typename Value> -FieldHandlerMap<Context...>::FieldMap<Value>::FieldMap(FieldMap&& that) noexcept - : num_small_keys_( - std::exchange(that.num_small_keys_, kPoisonedNumSmallKeys)), - small_map_(std::move(that.small_map_)), - small_values_(std::move(that.small_values_)), - large_map_(std::move(that.large_map_)) {} - -template <typename... Context> -template <typename Value> -auto FieldHandlerMap<Context...>::FieldMap<Value>::operator=( - FieldMap&& that) noexcept -> FieldMap& { - num_small_keys_ = std::exchange(that.num_small_keys_, kPoisonedNumSmallKeys); - small_map_ = std::move(that.small_map_); - small_values_ = std::move(that.small_values_); - large_map_ = std::move(that.large_map_); - return *this; -} - -template <typename... Context> -template <typename Value> -void FieldHandlerMap<Context...>::FieldMap<Value>::Reset() { - num_small_keys_ = 0; - small_map_.reset(); - small_values_.reset(); - large_map_.reset(); -} - -template <typename... Context> -template <typename Value> -void FieldHandlerMap<Context...>::FieldMap<Value>::Optimize( - absl::flat_hash_map<int, Value>&& map) { - RIEGELI_ASSERT(!map.empty()) - << "Failed precondition of FieldHandlerMap::FieldMap::Optimize(): " - "an empty map must have been handled before"; - size_t max_key = 0; - for (const auto& entry : map) { - max_key = UnsignedMax(max_key, static_cast<size_t>(entry.first - 1)); - } - // Prevent `small_map_` from wasting too much memory. A field number not - // larger than 128 is suitable for `small_map_`. If `map` has many elements, - // `small_map_` can cover more field numbers if it is at least 25% full. - const size_t max_num_small_keys = UnsignedMax(size_t{128}, map.size() * 4); - if (max_key < max_num_small_keys) { - // All field numbers are suitable for `small_map_`. `large_map_` is not - // used. - // - // There is no need for `small_map_` to cover keys larger than `max_key`, - // because their lookup is fast if `large_map_` is `std::nullopt`. - num_small_keys_ = max_key + 1; - small_map_ = - std::make_unique<const Value* absl_nullable[]>(num_small_keys_); - small_values_ = std::make_unique<Value[]>(map.size()); - size_t small_values_index = 0; - for (auto& entry : map) { - Value* const value = &small_values_[small_values_index++]; - small_map_[IntCast<size_t>(entry.first - 1)] = value; - *value = std::move(entry.second); - } - } else { - // Some field numbers are too large for `small_map_`. `large_map_` is used. - // - // `small_map_` covers all keys below `max_num_small_keys` rather than only - // to `max_key`, to reduce lookups in `large_map_`. - num_small_keys_ = max_num_small_keys; - small_map_ = - std::make_unique<const Value* absl_nullable[]>(max_num_small_keys); - size_t num_small_values = 0; - for (const auto& entry : map) { - num_small_values += - static_cast<size_t>(entry.first - 1) < max_num_small_keys ? 1 : 0; - } - if (num_small_values > 0) { - small_values_ = std::make_unique<Value[]>(num_small_values); - } - large_map_.emplace(); - large_map_->reserve(map.size() - num_small_values); - size_t small_values_index = 0; - for (auto& entry : map) { - if (static_cast<size_t>(entry.first - 1) < max_num_small_keys) { - Value* const value = &small_values_[small_values_index++]; - small_map_[IntCast<size_t>(entry.first - 1)] = value; - *value = std::move(entry.second); - } else { - large_map_->try_emplace(entry.first, std::move(entry.second)); - } - } - RIEGELI_ASSERT_EQ(small_values_index, num_small_values) - << "The whole small_values_ array should have been filled"; - } -#if RIEGELI_DEBUG - // Detect using a moved-from `FieldHandlerMap::Builder` if using a moved-from - // `absl::flat_hash_map` is detected. - ABSL_ATTRIBUTE_UNUSED absl::flat_hash_map<int, Value> moved = std::move(map); -#endif -} - template <typename... Context> FieldHandlerMap<Context...>::FieldHandlerMap(Builder&& builder) - : varint_handlers_(std::move(builder.varint_handlers_)), - fixed32_handlers_(std::move(builder.fixed32_handlers_)), - fixed64_handlers_(std::move(builder.fixed64_handlers_)), - length_delimited_handlers_(std::move(builder.length_delimited_handlers_)), - start_group_handlers_(std::move(builder.start_group_handlers_)), - end_group_handlers_(std::move(builder.end_group_handlers_)) {} + : varint_handlers_( + std::make_move_iterator(builder.varint_handlers_.begin()), + std::make_move_iterator(builder.varint_handlers_.end()), + builder.varint_handlers_.size()), + fixed32_handlers_( + std::make_move_iterator(builder.fixed32_handlers_.begin()), + std::make_move_iterator(builder.fixed32_handlers_.end()), + builder.fixed32_handlers_.size()), + fixed64_handlers_( + std::make_move_iterator(builder.fixed64_handlers_.begin()), + std::make_move_iterator(builder.fixed64_handlers_.end()), + builder.fixed64_handlers_.size()), + length_delimited_handlers_( + std::make_move_iterator(builder.length_delimited_handlers_.begin()), + std::make_move_iterator(builder.length_delimited_handlers_.end()), + builder.length_delimited_handlers_.size()), + start_group_handlers_( + std::make_move_iterator(builder.start_group_handlers_.begin()), + std::make_move_iterator(builder.start_group_handlers_.end()), + builder.start_group_handlers_.size()), + end_group_handlers_( + std::make_move_iterator(builder.end_group_handlers_.begin()), + std::make_move_iterator(builder.end_group_handlers_.end()), + builder.end_group_handlers_.size()) { +#if RIEGELI_DEBUG + // Detect using a moved-from `Builder` if using a moved-from + // `absl::flat_hash_map` is detected. + ABSL_ATTRIBUTE_UNUSED Builder moved = std::move(builder); +#endif +} template <typename... Context> void FieldHandlerMap<Context...>::Reset() { @@ -601,13 +452,35 @@ template <typename... Context> void FieldHandlerMap<Context...>::Reset(Builder&& builder) { - varint_handlers_.Reset(std::move(builder.varint_handlers_)); - fixed32_handlers_.Reset(std::move(builder.fixed32_handlers_)); - fixed64_handlers_.Reset(std::move(builder.fixed64_handlers_)); + varint_handlers_.Reset( + std::make_move_iterator(builder.varint_handlers_.begin()), + std::make_move_iterator(builder.varint_handlers_.end()), + builder.varint_handlers_.size()); + fixed32_handlers_.Reset( + std::make_move_iterator(builder.fixed32_handlers_.begin()), + std::make_move_iterator(builder.fixed32_handlers_.end()), + builder.fixed32_handlers_.size()); + fixed64_handlers_.Reset( + std::make_move_iterator(builder.fixed64_handlers_.begin()), + std::make_move_iterator(builder.fixed64_handlers_.end()), + builder.fixed64_handlers_.size()); length_delimited_handlers_.Reset( - std::move(builder.length_delimited_handlers_)); - start_group_handlers_.Reset(std::move(builder.start_group_handlers_)); - end_group_handlers_.Reset(std::move(builder.end_group_handlers_)); + std::make_move_iterator(builder.length_delimited_handlers_.begin()), + std::make_move_iterator(builder.length_delimited_handlers_.end()), + builder.length_delimited_handlers_.size()); + start_group_handlers_.Reset( + std::make_move_iterator(builder.start_group_handlers_.begin()), + std::make_move_iterator(builder.start_group_handlers_.end()), + builder.start_group_handlers_.size()); + end_group_handlers_.Reset( + std::make_move_iterator(builder.end_group_handlers_.begin()), + std::make_move_iterator(builder.end_group_handlers_.end()), + builder.end_group_handlers_.size()); +#if RIEGELI_DEBUG + // Detect using a moved-from `Builder` if using a moved-from + // `absl::flat_hash_map` is detected. + ABSL_ATTRIBUTE_UNUSED Builder moved = std::move(builder); +#endif } } // namespace riegeli