Add `UnboundFieldCopier()` for registering field copiers with a
`FieldHandlerMap`.

PiperOrigin-RevId: 897171160
diff --git a/riegeli/messages/field_copier.h b/riegeli/messages/field_copier.h
index 796f8cc..58243db 100644
--- a/riegeli/messages/field_copier.h
+++ b/riegeli/messages/field_copier.h
@@ -51,6 +51,10 @@
 // The type returned by `AnyFieldCopier()`.
 using AnyFieldCopierType = DynamicFieldCopierType<AcceptAnyField>;
 
+// The type returned by `UnboundFieldCopier()`.
+template <WireTypeSet wire_types = AllWireTypes>
+class UnboundFieldCopierType;
+
 // A field handler for `SerializedMessageReader` which copies the given field to
 // a `SerializedMessageWriter`.
 //
@@ -94,6 +98,22 @@
 // `SerializedMessageWriter` if this is not the case.
 constexpr AnyFieldCopierType AnyFieldCopier();
 
+// An unbound field handler for a `DynamicFieldHandler` or `FieldHandlerMap`
+// which copies the current field to a `SerializedMessageWriter`, with the
+// source and destination field numbers specified at runtime.
+//
+// As an optimization, `wire_types` constrains the set of wire types to handle.
+// This yields smaller and faster code.
+//
+// `Context...` types must contain exactly one occurrence of
+// `SerializedMessageWriter`. Use `ContextProjection()` to select the
+// `SerializedMessageWriter` if this is not the case.
+template <WireTypeSet wire_types = AllWireTypes>
+constexpr UnboundFieldCopierType<wire_types> UnboundFieldCopier(
+    int field_number) {
+  return UnboundFieldCopierType<wire_types>(field_number);
+}
+
 // Implementation details follow.
 
 template <int field_number, WireTypeSet wire_types>
@@ -348,6 +368,102 @@
   return AnyFieldCopierType(AcceptAnyField());
 }
 
+template <WireTypeSet wire_types>
+class UnboundFieldCopierType {
+ public:
+  static constexpr int kFieldNumber = kUnboundFieldNumber;
+
+  constexpr UnboundFieldCopierType(int field_number)
+      : field_number_(field_number) {}
+
+  UnboundFieldCopierType(const UnboundFieldCopierType& that) = default;
+  UnboundFieldCopierType& operator=(const UnboundFieldCopierType& that) =
+      default;
+
+  template <typename... Context, WireTypeSet dependent_wire_types = wire_types,
+            std::enable_if_t<(dependent_wire_types & WireTypeSet::kVarint) ==
+                                 WireTypeSet::kVarint,
+                             int> = 0>
+  absl::Status HandleVarint(uint64_t repr, Context&... context) const {
+    return message_writer(context...).WriteUInt64(field_number_, repr);
+  }
+
+  template <typename... Context, WireTypeSet dependent_wire_types = wire_types,
+            std::enable_if_t<(dependent_wire_types & WireTypeSet::kFixed32) ==
+                                 WireTypeSet::kFixed32,
+                             int> = 0>
+  absl::Status HandleFixed32(uint32_t repr, Context&... context) const {
+    return message_writer(context...).WriteFixed32(field_number_, repr);
+  }
+
+  template <typename... Context, WireTypeSet dependent_wire_types = wire_types,
+            std::enable_if_t<(dependent_wire_types & WireTypeSet::kFixed64) ==
+                                 WireTypeSet::kFixed64,
+                             int> = 0>
+  absl::Status HandleFixed64(uint64_t repr, Context&... context) const {
+    return message_writer(context...).WriteFixed64(field_number_, repr);
+  }
+
+  template <
+      typename... Context, WireTypeSet dependent_wire_types = wire_types,
+      std::enable_if_t<(dependent_wire_types & WireTypeSet::kLengthDelimited) ==
+                           WireTypeSet::kLengthDelimited,
+                       int> = 0>
+  absl::Status HandleLengthDelimitedFromReader(ReaderSpan<> repr,
+                                               Context&... context) const {
+    return message_writer(context...)
+        .WriteString(field_number_, std::move(repr));
+  }
+
+  template <
+      typename... Context, WireTypeSet dependent_wire_types = wire_types,
+      std::enable_if_t<(dependent_wire_types & WireTypeSet::kLengthDelimited) ==
+                           WireTypeSet::kLengthDelimited,
+                       int> = 0>
+  absl::Status HandleLengthDelimitedFromCord(
+      CordIteratorSpan repr, ABSL_ATTRIBUTE_UNUSED std::string& scratch,
+      Context&... context) const {
+    return message_writer(context...)
+        .WriteString(field_number_, std::move(repr));
+  }
+
+  template <
+      typename... Context, WireTypeSet dependent_wire_types = wire_types,
+      std::enable_if_t<(dependent_wire_types & WireTypeSet::kLengthDelimited) ==
+                           WireTypeSet::kLengthDelimited,
+                       int> = 0>
+  absl::Status HandleLengthDelimitedFromString(absl::string_view repr,
+                                               Context&... context) const {
+    return message_writer(context...).WriteString(field_number_, repr);
+  }
+
+  template <
+      typename... Context, WireTypeSet dependent_wire_types = wire_types,
+      std::enable_if_t<(dependent_wire_types & WireTypeSet::kStartGroup) ==
+                           WireTypeSet::kStartGroup,
+                       int> = 0>
+  absl::Status HandleStartGroup(Context&... context) const {
+    return message_writer(context...).OpenGroup(field_number_);
+  }
+
+  template <typename... Context, WireTypeSet dependent_wire_types = wire_types,
+            std::enable_if_t<(dependent_wire_types & WireTypeSet::kEndGroup) ==
+                                 WireTypeSet::kEndGroup,
+                             int> = 0>
+  absl::Status HandleEndGroup(Context&... context) const {
+    return message_writer(context...).CloseGroup(field_number_);
+  }
+
+ private:
+  template <typename... Context>
+  static SerializedMessageWriter& message_writer(Context&... context) {
+    return std::get<SerializedMessageWriter&>(
+        std::tuple<Context&...>(context...));
+  }
+
+  int field_number_;
+};
+
 }  // namespace riegeli
 
 #endif  // RIEGELI_MESSAGES_FIELD_COPIER_H_