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