This is an automated email from the ASF dual-hosted git repository.

chaokunyang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fory.git


The following commit(s) were added to refs/heads/main by this push:
     new ba433e688 feat(c++): add iterator container serialization support 
(#3068)
ba433e688 is described below

commit ba433e6889c01eabda1856167d8dca0e9c106a5d
Author: zhan7236 <[email protected]>
AuthorDate: Sun Dec 21 00:43:06 2025 +0800

    feat(c++): add iterator container serialization support (#3068)
    
    ## Why?
    
    Per xlang specification, Fory needs to support serialization of
    iterator-based containers. Currently, only `std::vector` and `std::set`
    are supported in C++. This PR adds support for additional sequence
    containers (`std::list`, `std::deque`, `std::forward_list`) to provide
    more flexibility for C++ users.
    
    ## What does this PR do?
    
    Add serialization support for C++ iterator containers:
    - `std::list<T>`
    - `std::deque<T>`
    - `std::forward_list<T>`
    
    All these containers are serialized as `TypeId::LIST` (value 21) per
    xlang specification, which only distinguishes between LIST and SET
    collection types. Set-like classes (`std::set`, `std::unordered_set`)
    continue to use `TypeId::SET`.
    
    **Changes:**
    - `serializer_traits.h`: Add `is_list`, `is_deque`, `is_forward_list`
    type traits and `is_generic_type` specializations
    - `collection_serializer.h`: Add complete `Serializer` specializations
    for `std::list`, `std::deque`, `std::forward_list` with full read/write
    support
    - `type_resolver.h`: Add `FieldTypeBuilder` specializations for new
    container types to support struct field serialization
    - `collection_serializer_test.cc`: Add 9 comprehensive test cases
    covering string, integer, and empty collection scenarios
    
    ## Related issues
    
    Closes #2911
    
    ## Does this PR introduce any user-facing change?
    
    - [x] Does this PR introduce any public API change?
    - Adds new public serialization support for `std::list`, `std::deque`,
    and `std::forward_list` containers
    - [ ] Does this PR introduce any binary protocol compatibility change?
      - No, uses existing `TypeId::LIST` protocol
    
    ## Benchmark
    
    N/A - This PR adds new functionality without modifying existing
    serialization paths. The new container serializers follow the same
    patterns as the existing `std::vector` serializer.
---
 cpp/fory/serialization/collection_serializer.h     | 802 +++++++++++++++++++++
 .../serialization/collection_serializer_test.cc    | 251 +++++++
 cpp/fory/serialization/serializer_traits.h         |  41 ++
 cpp/fory/serialization/type_resolver.h             |  51 +-
 4 files changed, 1138 insertions(+), 7 deletions(-)

diff --git a/cpp/fory/serialization/collection_serializer.h 
b/cpp/fory/serialization/collection_serializer.h
index e3d27e3a8..e83ae15da 100644
--- a/cpp/fory/serialization/collection_serializer.h
+++ b/cpp/fory/serialization/collection_serializer.h
@@ -23,7 +23,10 @@
 #include "fory/serialization/serializer.h"
 #include <array>
 #include <cstdint>
+#include <deque>
+#include <forward_list>
 #include <limits>
+#include <list>
 #include <set>
 #include <typeindex>
 #include <unordered_set>
@@ -898,6 +901,805 @@ template <typename Alloc> struct 
Serializer<std::vector<bool, Alloc>> {
   }
 };
 
+// ============================================================================
+// std::list serializer
+// ============================================================================
+
+template <typename T, typename Alloc> struct Serializer<std::list<T, Alloc>> {
+  static constexpr TypeId type_id = TypeId::LIST;
+
+  static inline void write_type_info(WriteContext &ctx) {
+    ctx.write_varuint32(static_cast<uint32_t>(type_id));
+  }
+
+  static inline void read_type_info(ReadContext &ctx) {
+    uint32_t actual = ctx.read_varuint32(ctx.error());
+    if (FORY_PREDICT_FALSE(ctx.has_error())) {
+      return;
+    }
+    if (!type_id_matches(actual, static_cast<uint32_t>(type_id))) {
+      ctx.set_error(
+          Error::type_mismatch(actual, static_cast<uint32_t>(type_id)));
+    }
+  }
+
+  static inline std::list<T, Alloc> read(ReadContext &ctx, bool read_ref,
+                                         bool read_type) {
+    // List-level reference flag
+    bool has_value = consume_ref_flag(ctx, read_ref);
+    if (ctx.has_error() || !has_value) {
+      return std::list<T, Alloc>();
+    }
+
+    // Optional type info for polymorphic containers
+    if (read_type) {
+      uint32_t type_id_read = ctx.read_varuint32(ctx.error());
+      if (FORY_PREDICT_FALSE(ctx.has_error())) {
+        return std::list<T, Alloc>();
+      }
+      uint32_t low = type_id_read & 0xffu;
+      if (low != static_cast<uint32_t>(type_id)) {
+        ctx.set_error(
+            Error::type_mismatch(type_id_read, 
static_cast<uint32_t>(type_id)));
+        return std::list<T, Alloc>();
+      }
+    }
+
+    // Length written via writeVarUint32Small7
+    uint32_t length = ctx.read_varuint32(ctx.error());
+    if (FORY_PREDICT_FALSE(ctx.has_error())) {
+      return std::list<T, Alloc>();
+    }
+    // Per xlang spec: header and type_info are omitted when length is 0
+    if (length == 0) {
+      return std::list<T, Alloc>();
+    }
+
+    // Dispatch to slow path for polymorphic/shared-ref elements
+    constexpr bool is_slow_path = is_polymorphic_v<T> || is_shared_ref_v<T>;
+    if constexpr (is_slow_path) {
+      return read_collection_data_slow<T, std::list<T, Alloc>>(ctx, length);
+    } else {
+      // Fast path for non-polymorphic, non-shared-ref elements
+
+      // Elements header bitmap (CollectionFlags)
+      uint8_t bitmap = ctx.read_uint8(ctx.error());
+      if (FORY_PREDICT_FALSE(ctx.has_error())) {
+        return std::list<T, Alloc>();
+      }
+      bool track_ref = (bitmap & COLL_TRACKING_REF) != 0;
+      bool has_null = (bitmap & COLL_HAS_NULL) != 0;
+      bool is_decl_type = (bitmap & COLL_DECL_ELEMENT_TYPE) != 0;
+      bool is_same_type = (bitmap & COLL_IS_SAME_TYPE) != 0;
+
+      // Read element type info if IS_SAME_TYPE is set but IS_DECL_ELEMENT_TYPE
+      // is not.
+      if (is_same_type && !is_decl_type) {
+        const TypeInfo *elem_type_info = ctx.read_any_typeinfo(ctx.error());
+        if (FORY_PREDICT_FALSE(ctx.has_error())) {
+          return std::list<T, Alloc>();
+        }
+        using ElemType = nullable_element_t<T>;
+        uint32_t expected =
+            static_cast<uint32_t>(Serializer<ElemType>::type_id);
+        if (!type_id_matches(elem_type_info->type_id, expected)) {
+          ctx.set_error(
+              Error::type_mismatch(elem_type_info->type_id, expected));
+          return std::list<T, Alloc>();
+        }
+      }
+
+      std::list<T, Alloc> result;
+
+      // Fast path: no tracking, no nulls, elements have declared type
+      if (!track_ref && !has_null && is_same_type) {
+        for (uint32_t i = 0; i < length; ++i) {
+          if (FORY_PREDICT_FALSE(ctx.has_error())) {
+            return result;
+          }
+          auto elem = Serializer<T>::read(ctx, false, false);
+          result.push_back(std::move(elem));
+        }
+        return result;
+      }
+
+      // General path: handle HAS_NULL and/or TRACKING_REF
+      for (uint32_t i = 0; i < length; ++i) {
+        if (FORY_PREDICT_FALSE(ctx.has_error())) {
+          return result;
+        }
+        if (track_ref) {
+          auto elem = Serializer<T>::read(ctx, true, false);
+          result.push_back(std::move(elem));
+        } else if (has_null) {
+          bool has_value_elem = consume_ref_flag(ctx, true);
+          if (!has_value_elem) {
+            result.emplace_back();
+          } else {
+            if constexpr (is_nullable_v<T>) {
+              using Inner = nullable_element_t<T>;
+              auto inner = Serializer<Inner>::read(ctx, false, false);
+              result.emplace_back(std::move(inner));
+            } else {
+              auto elem = Serializer<T>::read(ctx, false, false);
+              result.push_back(std::move(elem));
+            }
+          }
+        } else {
+          auto elem = Serializer<T>::read(ctx, false, false);
+          result.push_back(std::move(elem));
+        }
+      }
+
+      return result;
+    }
+  }
+
+  static inline void write(const std::list<T, Alloc> &lst, WriteContext &ctx,
+                           bool write_ref, bool write_type,
+                           bool has_generics = false) {
+    // Write ref flag if requested
+    write_not_null_ref_flag(ctx, write_ref);
+
+    // Write type info if requested
+    if (write_type) {
+      ctx.write_varuint32(static_cast<uint32_t>(type_id));
+    }
+
+    write_data_generic(lst, ctx, has_generics);
+  }
+
+  static inline void write_data(const std::list<T, Alloc> &lst,
+                                WriteContext &ctx) {
+    ctx.write_varuint32(static_cast<uint32_t>(lst.size()));
+    for (const auto &elem : lst) {
+      Serializer<T>::write_data(elem, ctx);
+    }
+  }
+
+  static inline void write_data_generic(const std::list<T, Alloc> &lst,
+                                        WriteContext &ctx, bool has_generics) {
+    // Dispatch to fast or slow path based on element type characteristics
+    constexpr bool is_fast_path = !is_polymorphic_v<T> && !is_shared_ref_v<T>;
+
+    if constexpr (is_fast_path) {
+      write_collection_data_fast<T>(lst, ctx, has_generics);
+    } else {
+      write_collection_data_slow<T>(lst, ctx, has_generics);
+    }
+  }
+
+  static inline std::list<T, Alloc>
+  read_with_type_info(ReadContext &ctx, bool read_ref,
+                      const TypeInfo &type_info) {
+    return read(ctx, read_ref, false);
+  }
+
+  static inline std::list<T, Alloc> read_data(ReadContext &ctx) {
+    uint32_t size = ctx.read_varuint32(ctx.error());
+    if (FORY_PREDICT_FALSE(ctx.has_error())) {
+      return std::list<T, Alloc>();
+    }
+    std::list<T, Alloc> result;
+    for (uint32_t i = 0; i < size; ++i) {
+      if (FORY_PREDICT_FALSE(ctx.has_error())) {
+        return result;
+      }
+      auto elem = Serializer<T>::read_data(ctx);
+      result.push_back(std::move(elem));
+    }
+    return result;
+  }
+};
+
+// ============================================================================
+// std::deque serializer
+// ============================================================================
+
+template <typename T, typename Alloc> struct Serializer<std::deque<T, Alloc>> {
+  static constexpr TypeId type_id = TypeId::LIST;
+
+  static inline void write_type_info(WriteContext &ctx) {
+    ctx.write_varuint32(static_cast<uint32_t>(type_id));
+  }
+
+  static inline void read_type_info(ReadContext &ctx) {
+    uint32_t actual = ctx.read_varuint32(ctx.error());
+    if (FORY_PREDICT_FALSE(ctx.has_error())) {
+      return;
+    }
+    if (!type_id_matches(actual, static_cast<uint32_t>(type_id))) {
+      ctx.set_error(
+          Error::type_mismatch(actual, static_cast<uint32_t>(type_id)));
+    }
+  }
+
+  static inline std::deque<T, Alloc> read(ReadContext &ctx, bool read_ref,
+                                          bool read_type) {
+    // Deque-level reference flag
+    bool has_value = consume_ref_flag(ctx, read_ref);
+    if (ctx.has_error() || !has_value) {
+      return std::deque<T, Alloc>();
+    }
+
+    // Optional type info for polymorphic containers
+    if (read_type) {
+      uint32_t type_id_read = ctx.read_varuint32(ctx.error());
+      if (FORY_PREDICT_FALSE(ctx.has_error())) {
+        return std::deque<T, Alloc>();
+      }
+      uint32_t low = type_id_read & 0xffu;
+      if (low != static_cast<uint32_t>(type_id)) {
+        ctx.set_error(
+            Error::type_mismatch(type_id_read, 
static_cast<uint32_t>(type_id)));
+        return std::deque<T, Alloc>();
+      }
+    }
+
+    // Length written via writeVarUint32Small7
+    uint32_t length = ctx.read_varuint32(ctx.error());
+    if (FORY_PREDICT_FALSE(ctx.has_error())) {
+      return std::deque<T, Alloc>();
+    }
+    // Per xlang spec: header and type_info are omitted when length is 0
+    if (length == 0) {
+      return std::deque<T, Alloc>();
+    }
+
+    // Dispatch to slow path for polymorphic/shared-ref elements
+    constexpr bool is_slow_path = is_polymorphic_v<T> || is_shared_ref_v<T>;
+    if constexpr (is_slow_path) {
+      return read_collection_data_slow<T, std::deque<T, Alloc>>(ctx, length);
+    } else {
+      // Fast path for non-polymorphic, non-shared-ref elements
+
+      // Elements header bitmap (CollectionFlags)
+      uint8_t bitmap = ctx.read_uint8(ctx.error());
+      if (FORY_PREDICT_FALSE(ctx.has_error())) {
+        return std::deque<T, Alloc>();
+      }
+      bool track_ref = (bitmap & COLL_TRACKING_REF) != 0;
+      bool has_null = (bitmap & COLL_HAS_NULL) != 0;
+      bool is_decl_type = (bitmap & COLL_DECL_ELEMENT_TYPE) != 0;
+      bool is_same_type = (bitmap & COLL_IS_SAME_TYPE) != 0;
+
+      // Read element type info if IS_SAME_TYPE is set but IS_DECL_ELEMENT_TYPE
+      // is not.
+      if (is_same_type && !is_decl_type) {
+        const TypeInfo *elem_type_info = ctx.read_any_typeinfo(ctx.error());
+        if (FORY_PREDICT_FALSE(ctx.has_error())) {
+          return std::deque<T, Alloc>();
+        }
+        using ElemType = nullable_element_t<T>;
+        uint32_t expected =
+            static_cast<uint32_t>(Serializer<ElemType>::type_id);
+        if (!type_id_matches(elem_type_info->type_id, expected)) {
+          ctx.set_error(
+              Error::type_mismatch(elem_type_info->type_id, expected));
+          return std::deque<T, Alloc>();
+        }
+      }
+
+      std::deque<T, Alloc> result;
+
+      // Fast path: no tracking, no nulls, elements have declared type
+      if (!track_ref && !has_null && is_same_type) {
+        for (uint32_t i = 0; i < length; ++i) {
+          if (FORY_PREDICT_FALSE(ctx.has_error())) {
+            return result;
+          }
+          auto elem = Serializer<T>::read(ctx, false, false);
+          result.push_back(std::move(elem));
+        }
+        return result;
+      }
+
+      // General path: handle HAS_NULL and/or TRACKING_REF
+      for (uint32_t i = 0; i < length; ++i) {
+        if (FORY_PREDICT_FALSE(ctx.has_error())) {
+          return result;
+        }
+        if (track_ref) {
+          auto elem = Serializer<T>::read(ctx, true, false);
+          result.push_back(std::move(elem));
+        } else if (has_null) {
+          bool has_value_elem = consume_ref_flag(ctx, true);
+          if (!has_value_elem) {
+            result.emplace_back();
+          } else {
+            if constexpr (is_nullable_v<T>) {
+              using Inner = nullable_element_t<T>;
+              auto inner = Serializer<Inner>::read(ctx, false, false);
+              result.emplace_back(std::move(inner));
+            } else {
+              auto elem = Serializer<T>::read(ctx, false, false);
+              result.push_back(std::move(elem));
+            }
+          }
+        } else {
+          auto elem = Serializer<T>::read(ctx, false, false);
+          result.push_back(std::move(elem));
+        }
+      }
+
+      return result;
+    }
+  }
+
+  static inline void write(const std::deque<T, Alloc> &deq, WriteContext &ctx,
+                           bool write_ref, bool write_type,
+                           bool has_generics = false) {
+    // Write ref flag if requested
+    write_not_null_ref_flag(ctx, write_ref);
+
+    // Write type info if requested
+    if (write_type) {
+      ctx.write_varuint32(static_cast<uint32_t>(type_id));
+    }
+
+    write_data_generic(deq, ctx, has_generics);
+  }
+
+  static inline void write_data(const std::deque<T, Alloc> &deq,
+                                WriteContext &ctx) {
+    ctx.write_varuint32(static_cast<uint32_t>(deq.size()));
+    for (const auto &elem : deq) {
+      Serializer<T>::write_data(elem, ctx);
+    }
+  }
+
+  static inline void write_data_generic(const std::deque<T, Alloc> &deq,
+                                        WriteContext &ctx, bool has_generics) {
+    // Dispatch to fast or slow path based on element type characteristics
+    constexpr bool is_fast_path = !is_polymorphic_v<T> && !is_shared_ref_v<T>;
+
+    if constexpr (is_fast_path) {
+      write_collection_data_fast<T>(deq, ctx, has_generics);
+    } else {
+      write_collection_data_slow<T>(deq, ctx, has_generics);
+    }
+  }
+
+  static inline std::deque<T, Alloc>
+  read_with_type_info(ReadContext &ctx, bool read_ref,
+                      const TypeInfo &type_info) {
+    return read(ctx, read_ref, false);
+  }
+
+  static inline std::deque<T, Alloc> read_data(ReadContext &ctx) {
+    uint32_t size = ctx.read_varuint32(ctx.error());
+    if (FORY_PREDICT_FALSE(ctx.has_error())) {
+      return std::deque<T, Alloc>();
+    }
+    std::deque<T, Alloc> result;
+    for (uint32_t i = 0; i < size; ++i) {
+      if (FORY_PREDICT_FALSE(ctx.has_error())) {
+        return result;
+      }
+      auto elem = Serializer<T>::read_data(ctx);
+      result.push_back(std::move(elem));
+    }
+    return result;
+  }
+};
+
+// ============================================================================
+// std::forward_list serializer
+// ============================================================================
+
+template <typename T, typename Alloc>
+struct Serializer<std::forward_list<T, Alloc>> {
+  static constexpr TypeId type_id = TypeId::LIST;
+
+  static inline void write_type_info(WriteContext &ctx) {
+    ctx.write_varuint32(static_cast<uint32_t>(type_id));
+  }
+
+  static inline void read_type_info(ReadContext &ctx) {
+    uint32_t actual = ctx.read_varuint32(ctx.error());
+    if (FORY_PREDICT_FALSE(ctx.has_error())) {
+      return;
+    }
+    if (!type_id_matches(actual, static_cast<uint32_t>(type_id))) {
+      ctx.set_error(
+          Error::type_mismatch(actual, static_cast<uint32_t>(type_id)));
+    }
+  }
+
+  static inline std::forward_list<T, Alloc>
+  read(ReadContext &ctx, bool read_ref, bool read_type) {
+    // List-level reference flag
+    bool has_value = consume_ref_flag(ctx, read_ref);
+    if (ctx.has_error() || !has_value) {
+      return std::forward_list<T, Alloc>();
+    }
+
+    // Optional type info for polymorphic containers
+    if (read_type) {
+      uint32_t type_id_read = ctx.read_varuint32(ctx.error());
+      if (FORY_PREDICT_FALSE(ctx.has_error())) {
+        return std::forward_list<T, Alloc>();
+      }
+      uint32_t low = type_id_read & 0xffu;
+      if (low != static_cast<uint32_t>(type_id)) {
+        ctx.set_error(
+            Error::type_mismatch(type_id_read, 
static_cast<uint32_t>(type_id)));
+        return std::forward_list<T, Alloc>();
+      }
+    }
+
+    // Length written via writeVarUint32Small7
+    uint32_t length = ctx.read_varuint32(ctx.error());
+    if (FORY_PREDICT_FALSE(ctx.has_error())) {
+      return std::forward_list<T, Alloc>();
+    }
+    // Per xlang spec: header and type_info are omitted when length is 0
+    if (length == 0) {
+      return std::forward_list<T, Alloc>();
+    }
+
+    // Read elements into a temporary vector then build forward_list
+    // (forward_list doesn't have push_back, only push_front)
+    std::vector<T> temp;
+    temp.reserve(length);
+
+    // Dispatch to slow path for polymorphic/shared-ref elements
+    constexpr bool is_slow_path = is_polymorphic_v<T> || is_shared_ref_v<T>;
+    if constexpr (is_slow_path) {
+      temp = read_collection_data_slow<T, std::vector<T>>(ctx, length);
+    } else {
+      // Fast path for non-polymorphic, non-shared-ref elements
+
+      // Elements header bitmap (CollectionFlags)
+      uint8_t bitmap = ctx.read_uint8(ctx.error());
+      if (FORY_PREDICT_FALSE(ctx.has_error())) {
+        return std::forward_list<T, Alloc>();
+      }
+      bool track_ref = (bitmap & COLL_TRACKING_REF) != 0;
+      bool has_null = (bitmap & COLL_HAS_NULL) != 0;
+      bool is_decl_type = (bitmap & COLL_DECL_ELEMENT_TYPE) != 0;
+      bool is_same_type = (bitmap & COLL_IS_SAME_TYPE) != 0;
+
+      // Read element type info if IS_SAME_TYPE is set but IS_DECL_ELEMENT_TYPE
+      // is not.
+      if (is_same_type && !is_decl_type) {
+        const TypeInfo *elem_type_info = ctx.read_any_typeinfo(ctx.error());
+        if (FORY_PREDICT_FALSE(ctx.has_error())) {
+          return std::forward_list<T, Alloc>();
+        }
+        using ElemType = nullable_element_t<T>;
+        uint32_t expected =
+            static_cast<uint32_t>(Serializer<ElemType>::type_id);
+        if (!type_id_matches(elem_type_info->type_id, expected)) {
+          ctx.set_error(
+              Error::type_mismatch(elem_type_info->type_id, expected));
+          return std::forward_list<T, Alloc>();
+        }
+      }
+
+      // Fast path: no tracking, no nulls, elements have declared type
+      if (!track_ref && !has_null && is_same_type) {
+        for (uint32_t i = 0; i < length; ++i) {
+          if (FORY_PREDICT_FALSE(ctx.has_error())) {
+            break;
+          }
+          auto elem = Serializer<T>::read(ctx, false, false);
+          temp.push_back(std::move(elem));
+        }
+      } else {
+        // General path: handle HAS_NULL and/or TRACKING_REF
+        for (uint32_t i = 0; i < length; ++i) {
+          if (FORY_PREDICT_FALSE(ctx.has_error())) {
+            break;
+          }
+          if (track_ref) {
+            auto elem = Serializer<T>::read(ctx, true, false);
+            temp.push_back(std::move(elem));
+          } else if (has_null) {
+            bool has_value_elem = consume_ref_flag(ctx, true);
+            if (!has_value_elem) {
+              temp.emplace_back();
+            } else {
+              if constexpr (is_nullable_v<T>) {
+                using Inner = nullable_element_t<T>;
+                auto inner = Serializer<Inner>::read(ctx, false, false);
+                temp.emplace_back(std::move(inner));
+              } else {
+                auto elem = Serializer<T>::read(ctx, false, false);
+                temp.push_back(std::move(elem));
+              }
+            }
+          } else {
+            auto elem = Serializer<T>::read(ctx, false, false);
+            temp.push_back(std::move(elem));
+          }
+        }
+      }
+    }
+
+    // Build forward_list in reverse order using push_front
+    std::forward_list<T, Alloc> result;
+    for (auto it = temp.rbegin(); it != temp.rend(); ++it) {
+      result.push_front(std::move(*it));
+    }
+    return result;
+  }
+
+  static inline void write(const std::forward_list<T, Alloc> &lst,
+                           WriteContext &ctx, bool write_ref, bool write_type,
+                           bool has_generics = false) {
+    // Write ref flag if requested
+    write_not_null_ref_flag(ctx, write_ref);
+
+    // Write type info if requested
+    if (write_type) {
+      ctx.write_varuint32(static_cast<uint32_t>(type_id));
+    }
+
+    write_data_generic(lst, ctx, has_generics);
+  }
+
+  static inline void write_data(const std::forward_list<T, Alloc> &lst,
+                                WriteContext &ctx) {
+    // forward_list doesn't have size(), so we need to count elements first
+    uint32_t size = 0;
+    for (const auto &elem : lst) {
+      (void)elem;
+      ++size;
+    }
+    ctx.write_varuint32(size);
+    for (const auto &elem : lst) {
+      Serializer<T>::write_data(elem, ctx);
+    }
+  }
+
+  static inline void write_data_generic(const std::forward_list<T, Alloc> &lst,
+                                        WriteContext &ctx, bool has_generics) {
+    // Convert to vector first for efficient writing (forward_list has no size)
+    std::vector<std::reference_wrapper<const T>> temp;
+    for (const auto &elem : lst) {
+      temp.push_back(std::cref(elem));
+    }
+
+    // Write length
+    ctx.write_varuint32(static_cast<uint32_t>(temp.size()));
+
+    if (temp.empty()) {
+      return;
+    }
+
+    // Dispatch to fast or slow path based on element type characteristics
+    constexpr bool is_fast_path = !is_polymorphic_v<T> && !is_shared_ref_v<T>;
+
+    if constexpr (is_fast_path) {
+      // Check for null elements
+      bool has_null = false;
+      if constexpr (is_nullable_v<T>) {
+        for (const auto &elem_ref : temp) {
+          if (is_null_value(elem_ref.get())) {
+            has_null = true;
+            break;
+          }
+        }
+      }
+
+      // Build header bitmap
+      uint8_t bitmap = COLL_IS_SAME_TYPE;
+      if (has_null) {
+        bitmap |= COLL_HAS_NULL;
+      }
+
+      // Determine if element type is declared
+      using ElemType = nullable_element_t<T>;
+      bool is_elem_declared =
+          has_generics && !need_type_for_collection_elem<ElemType>();
+      if (is_elem_declared) {
+        bitmap |= COLL_DECL_ELEMENT_TYPE;
+      }
+
+      // Write header
+      ctx.write_uint8(bitmap);
+
+      // Write element type info if not declared
+      if (!is_elem_declared) {
+        Serializer<ElemType>::write_type_info(ctx);
+      }
+
+      // Write elements
+      if constexpr (is_nullable_v<T>) {
+        using Inner = nullable_element_t<T>;
+        if (has_null) {
+          for (const auto &elem_ref : temp) {
+            const auto &elem = elem_ref.get();
+            if (is_null_value(elem)) {
+              ctx.write_int8(NULL_FLAG);
+            } else {
+              ctx.write_int8(NOT_NULL_VALUE_FLAG);
+              if (is_elem_declared) {
+                Serializer<Inner>::write_data(deref_nullable(elem), ctx);
+              } else {
+                Serializer<Inner>::write(deref_nullable(elem), ctx, false,
+                                         false);
+              }
+            }
+          }
+        } else {
+          for (const auto &elem_ref : temp) {
+            const auto &elem = elem_ref.get();
+            if (is_elem_declared) {
+              Serializer<Inner>::write_data(deref_nullable(elem), ctx);
+            } else {
+              Serializer<Inner>::write(deref_nullable(elem), ctx, false, 
false);
+            }
+          }
+        }
+      } else {
+        for (const auto &elem_ref : temp) {
+          const auto &elem = elem_ref.get();
+          if (is_elem_declared) {
+            if constexpr (is_generic_type_v<T>) {
+              Serializer<T>::write_data_generic(elem, ctx, true);
+            } else {
+              Serializer<T>::write_data(elem, ctx);
+            }
+          } else {
+            Serializer<T>::write(elem, ctx, false, false);
+          }
+        }
+      }
+    } else {
+      // Slow path for polymorphic or shared-ref elements
+      constexpr bool elem_is_polymorphic = is_polymorphic_v<T>;
+      constexpr bool elem_is_shared_ref = is_shared_ref_v<T>;
+
+      using ElemType = nullable_element_t<T>;
+      bool is_elem_declared =
+          has_generics && !need_type_for_collection_elem<ElemType>();
+
+      // Scan collection to determine header flags
+      bool has_null = false;
+      bool is_same_type = true;
+      std::type_index first_type{typeid(void)};
+      bool first_type_set = false;
+
+      for (const auto &elem_ref : temp) {
+        const auto &elem = elem_ref.get();
+        // Check for nulls
+        if constexpr (is_nullable_v<T>) {
+          if (is_null_value(elem)) {
+            has_null = true;
+            continue;
+          }
+        }
+        // Check runtime types for polymorphic elements
+        if constexpr (elem_is_polymorphic) {
+          if (is_same_type) {
+            auto concrete_id = get_concrete_type_id(elem);
+            if (!first_type_set) {
+              first_type = concrete_id;
+              first_type_set = true;
+            } else if (concrete_id != first_type) {
+              is_same_type = false;
+            }
+          }
+        }
+      }
+
+      // If all polymorphic elements are null, treat as heterogeneous
+      if constexpr (elem_is_polymorphic) {
+        if (is_same_type && !first_type_set) {
+          is_same_type = false;
+        }
+      }
+
+      // Build header bitmap
+      uint8_t bitmap = 0;
+      if (has_null) {
+        bitmap |= COLL_HAS_NULL;
+      }
+      if (is_elem_declared && !elem_is_polymorphic) {
+        bitmap |= COLL_DECL_ELEMENT_TYPE;
+      }
+      if (is_same_type) {
+        bitmap |= COLL_IS_SAME_TYPE;
+      }
+      if constexpr (elem_is_shared_ref) {
+        bitmap |= COLL_TRACKING_REF;
+      }
+
+      // Write header
+      ctx.write_uint8(bitmap);
+
+      // Write element type info if IS_SAME_TYPE && !IS_DECL_ELEMENT_TYPE
+      if (is_same_type && !(bitmap & COLL_DECL_ELEMENT_TYPE)) {
+        if constexpr (elem_is_polymorphic) {
+          ctx.write_any_typeinfo(static_cast<uint32_t>(TypeId::UNKNOWN),
+                                 first_type);
+        } else {
+          Serializer<ElemType>::write_type_info(ctx);
+        }
+      }
+
+      // Write elements
+      if (is_same_type) {
+        if (!has_null) {
+          if constexpr (elem_is_shared_ref) {
+            for (const auto &elem_ref : temp) {
+              Serializer<T>::write(elem_ref.get(), ctx, true, false,
+                                   has_generics);
+            }
+          } else {
+            for (const auto &elem_ref : temp) {
+              const auto &elem = elem_ref.get();
+              if constexpr (is_nullable_v<T>) {
+                using Inner = nullable_element_t<T>;
+                Serializer<Inner>::write_data(deref_nullable(elem), ctx);
+              } else {
+                if constexpr (is_generic_type_v<T>) {
+                  Serializer<T>::write_data_generic(elem, ctx, has_generics);
+                } else {
+                  Serializer<T>::write_data(elem, ctx);
+                }
+              }
+            }
+          }
+        } else {
+          for (const auto &elem_ref : temp) {
+            Serializer<T>::write(elem_ref.get(), ctx, true, false,
+                                 has_generics);
+          }
+        }
+      } else {
+        if (!has_null) {
+          if constexpr (elem_is_shared_ref) {
+            for (const auto &elem_ref : temp) {
+              Serializer<T>::write(elem_ref.get(), ctx, true, true,
+                                   has_generics);
+            }
+          } else {
+            for (const auto &elem_ref : temp) {
+              Serializer<T>::write(elem_ref.get(), ctx, false, true,
+                                   has_generics);
+            }
+          }
+        } else {
+          for (const auto &elem_ref : temp) {
+            Serializer<T>::write(elem_ref.get(), ctx, true, true, 
has_generics);
+          }
+        }
+      }
+    }
+  }
+
+  static inline std::forward_list<T, Alloc>
+  read_with_type_info(ReadContext &ctx, bool read_ref,
+                      const TypeInfo &type_info) {
+    return read(ctx, read_ref, false);
+  }
+
+  static inline std::forward_list<T, Alloc> read_data(ReadContext &ctx) {
+    uint32_t size = ctx.read_varuint32(ctx.error());
+    if (FORY_PREDICT_FALSE(ctx.has_error())) {
+      return std::forward_list<T, Alloc>();
+    }
+    std::vector<T> temp;
+    temp.reserve(size);
+    for (uint32_t i = 0; i < size; ++i) {
+      if (FORY_PREDICT_FALSE(ctx.has_error())) {
+        break;
+      }
+      auto elem = Serializer<T>::read_data(ctx);
+      temp.push_back(std::move(elem));
+    }
+    // Build forward_list in reverse order
+    std::forward_list<T, Alloc> result;
+    for (auto it = temp.rbegin(); it != temp.rend(); ++it) {
+      result.push_front(std::move(*it));
+    }
+    return result;
+  }
+};
+
 // ============================================================================
 // std::set serializer
 // ============================================================================
diff --git a/cpp/fory/serialization/collection_serializer_test.cc 
b/cpp/fory/serialization/collection_serializer_test.cc
index 7e70c0758..e8dddf5f4 100644
--- a/cpp/fory/serialization/collection_serializer_test.cc
+++ b/cpp/fory/serialization/collection_serializer_test.cc
@@ -20,6 +20,9 @@
 #include "fory/serialization/fory.h"
 #include "gtest/gtest.h"
 #include <cstdint>
+#include <deque>
+#include <forward_list>
+#include <list>
 #include <memory>
 #include <optional>
 #include <string>
@@ -368,6 +371,254 @@ TEST(CollectionSerializerTest, VectorOptionalWithNulls) {
   EXPECT_EQ(*deserialized.values[2], "third");
 }
 
+// ============================================================================
+// std::list Tests
+// ============================================================================
+
+struct ListStringHolder {
+  std::list<std::string> strings;
+};
+FORY_STRUCT(ListStringHolder, strings);
+
+struct ListIntHolder {
+  std::list<int32_t> numbers;
+};
+FORY_STRUCT(ListIntHolder, numbers);
+
+TEST(CollectionSerializerTest, ListStringRoundTrip) {
+  auto fory = Fory::builder().xlang(true).build();
+  fory.register_struct<ListStringHolder>(300);
+
+  ListStringHolder original;
+  original.strings = {"hello", "world", "fory", "list"};
+
+  auto bytes_result = fory.serialize(original);
+  ASSERT_TRUE(bytes_result.ok()) << bytes_result.error().to_string();
+
+  auto deserialize_result = fory.deserialize<ListStringHolder>(
+      bytes_result->data(), bytes_result->size());
+  ASSERT_TRUE(deserialize_result.ok())
+      << deserialize_result.error().to_string();
+
+  auto deserialized = std::move(deserialize_result).value();
+  ASSERT_EQ(deserialized.strings.size(), 4u);
+  auto it = deserialized.strings.begin();
+  EXPECT_EQ(*it++, "hello");
+  EXPECT_EQ(*it++, "world");
+  EXPECT_EQ(*it++, "fory");
+  EXPECT_EQ(*it++, "list");
+}
+
+TEST(CollectionSerializerTest, ListIntRoundTrip) {
+  auto fory = Fory::builder().xlang(true).build();
+  fory.register_struct<ListIntHolder>(301);
+
+  ListIntHolder original;
+  original.numbers = {1, 2, 3, 42, 100};
+
+  auto bytes_result = fory.serialize(original);
+  ASSERT_TRUE(bytes_result.ok()) << bytes_result.error().to_string();
+
+  auto deserialize_result = fory.deserialize<ListIntHolder>(
+      bytes_result->data(), bytes_result->size());
+  ASSERT_TRUE(deserialize_result.ok())
+      << deserialize_result.error().to_string();
+
+  auto deserialized = std::move(deserialize_result).value();
+  ASSERT_EQ(deserialized.numbers.size(), 5u);
+  auto it = deserialized.numbers.begin();
+  EXPECT_EQ(*it++, 1);
+  EXPECT_EQ(*it++, 2);
+  EXPECT_EQ(*it++, 3);
+  EXPECT_EQ(*it++, 42);
+  EXPECT_EQ(*it++, 100);
+}
+
+TEST(CollectionSerializerTest, ListEmptyRoundTrip) {
+  auto fory = Fory::builder().xlang(true).build();
+  fory.register_struct<ListStringHolder>(302);
+
+  ListStringHolder original;
+  // Empty list
+
+  auto bytes_result = fory.serialize(original);
+  ASSERT_TRUE(bytes_result.ok()) << bytes_result.error().to_string();
+
+  auto deserialize_result = fory.deserialize<ListStringHolder>(
+      bytes_result->data(), bytes_result->size());
+  ASSERT_TRUE(deserialize_result.ok())
+      << deserialize_result.error().to_string();
+
+  auto deserialized = std::move(deserialize_result).value();
+  EXPECT_TRUE(deserialized.strings.empty());
+}
+
+// ============================================================================
+// std::deque Tests
+// ============================================================================
+
+struct DequeStringHolder {
+  std::deque<std::string> strings;
+};
+FORY_STRUCT(DequeStringHolder, strings);
+
+struct DequeIntHolder {
+  std::deque<int32_t> numbers;
+};
+FORY_STRUCT(DequeIntHolder, numbers);
+
+TEST(CollectionSerializerTest, DequeStringRoundTrip) {
+  auto fory = Fory::builder().xlang(true).build();
+  fory.register_struct<DequeStringHolder>(400);
+
+  DequeStringHolder original;
+  original.strings = {"hello", "world", "fory", "deque"};
+
+  auto bytes_result = fory.serialize(original);
+  ASSERT_TRUE(bytes_result.ok()) << bytes_result.error().to_string();
+
+  auto deserialize_result = fory.deserialize<DequeStringHolder>(
+      bytes_result->data(), bytes_result->size());
+  ASSERT_TRUE(deserialize_result.ok())
+      << deserialize_result.error().to_string();
+
+  auto deserialized = std::move(deserialize_result).value();
+  ASSERT_EQ(deserialized.strings.size(), 4u);
+  EXPECT_EQ(deserialized.strings[0], "hello");
+  EXPECT_EQ(deserialized.strings[1], "world");
+  EXPECT_EQ(deserialized.strings[2], "fory");
+  EXPECT_EQ(deserialized.strings[3], "deque");
+}
+
+TEST(CollectionSerializerTest, DequeIntRoundTrip) {
+  auto fory = Fory::builder().xlang(true).build();
+  fory.register_struct<DequeIntHolder>(401);
+
+  DequeIntHolder original;
+  original.numbers = {10, 20, 30, 40, 50};
+
+  auto bytes_result = fory.serialize(original);
+  ASSERT_TRUE(bytes_result.ok()) << bytes_result.error().to_string();
+
+  auto deserialize_result = fory.deserialize<DequeIntHolder>(
+      bytes_result->data(), bytes_result->size());
+  ASSERT_TRUE(deserialize_result.ok())
+      << deserialize_result.error().to_string();
+
+  auto deserialized = std::move(deserialize_result).value();
+  ASSERT_EQ(deserialized.numbers.size(), 5u);
+  EXPECT_EQ(deserialized.numbers[0], 10);
+  EXPECT_EQ(deserialized.numbers[1], 20);
+  EXPECT_EQ(deserialized.numbers[2], 30);
+  EXPECT_EQ(deserialized.numbers[3], 40);
+  EXPECT_EQ(deserialized.numbers[4], 50);
+}
+
+TEST(CollectionSerializerTest, DequeEmptyRoundTrip) {
+  auto fory = Fory::builder().xlang(true).build();
+  fory.register_struct<DequeStringHolder>(402);
+
+  DequeStringHolder original;
+  // Empty deque
+
+  auto bytes_result = fory.serialize(original);
+  ASSERT_TRUE(bytes_result.ok()) << bytes_result.error().to_string();
+
+  auto deserialize_result = fory.deserialize<DequeStringHolder>(
+      bytes_result->data(), bytes_result->size());
+  ASSERT_TRUE(deserialize_result.ok())
+      << deserialize_result.error().to_string();
+
+  auto deserialized = std::move(deserialize_result).value();
+  EXPECT_TRUE(deserialized.strings.empty());
+}
+
+// ============================================================================
+// std::forward_list Tests
+// ============================================================================
+
+struct ForwardListStringHolder {
+  std::forward_list<std::string> strings;
+};
+FORY_STRUCT(ForwardListStringHolder, strings);
+
+struct ForwardListIntHolder {
+  std::forward_list<int32_t> numbers;
+};
+FORY_STRUCT(ForwardListIntHolder, numbers);
+
+TEST(CollectionSerializerTest, ForwardListStringRoundTrip) {
+  auto fory = Fory::builder().xlang(true).build();
+  fory.register_struct<ForwardListStringHolder>(500);
+
+  ForwardListStringHolder original;
+  original.strings = {"hello", "world", "fory", "forward_list"};
+
+  auto bytes_result = fory.serialize(original);
+  ASSERT_TRUE(bytes_result.ok()) << bytes_result.error().to_string();
+
+  auto deserialize_result = fory.deserialize<ForwardListStringHolder>(
+      bytes_result->data(), bytes_result->size());
+  ASSERT_TRUE(deserialize_result.ok())
+      << deserialize_result.error().to_string();
+
+  auto deserialized = std::move(deserialize_result).value();
+  // Convert to vector for easier comparison
+  std::vector<std::string> result(deserialized.strings.begin(),
+                                  deserialized.strings.end());
+  ASSERT_EQ(result.size(), 4u);
+  EXPECT_EQ(result[0], "hello");
+  EXPECT_EQ(result[1], "world");
+  EXPECT_EQ(result[2], "fory");
+  EXPECT_EQ(result[3], "forward_list");
+}
+
+TEST(CollectionSerializerTest, ForwardListIntRoundTrip) {
+  auto fory = Fory::builder().xlang(true).build();
+  fory.register_struct<ForwardListIntHolder>(501);
+
+  ForwardListIntHolder original;
+  original.numbers = {100, 200, 300, 400, 500};
+
+  auto bytes_result = fory.serialize(original);
+  ASSERT_TRUE(bytes_result.ok()) << bytes_result.error().to_string();
+
+  auto deserialize_result = fory.deserialize<ForwardListIntHolder>(
+      bytes_result->data(), bytes_result->size());
+  ASSERT_TRUE(deserialize_result.ok())
+      << deserialize_result.error().to_string();
+
+  auto deserialized = std::move(deserialize_result).value();
+  // Convert to vector for easier comparison
+  std::vector<int32_t> result(deserialized.numbers.begin(),
+                              deserialized.numbers.end());
+  ASSERT_EQ(result.size(), 5u);
+  EXPECT_EQ(result[0], 100);
+  EXPECT_EQ(result[1], 200);
+  EXPECT_EQ(result[2], 300);
+  EXPECT_EQ(result[3], 400);
+  EXPECT_EQ(result[4], 500);
+}
+
+TEST(CollectionSerializerTest, ForwardListEmptyRoundTrip) {
+  auto fory = Fory::builder().xlang(true).build();
+  fory.register_struct<ForwardListStringHolder>(502);
+
+  ForwardListStringHolder original;
+  // Empty forward_list
+
+  auto bytes_result = fory.serialize(original);
+  ASSERT_TRUE(bytes_result.ok()) << bytes_result.error().to_string();
+
+  auto deserialize_result = fory.deserialize<ForwardListStringHolder>(
+      bytes_result->data(), bytes_result->size());
+  ASSERT_TRUE(deserialize_result.ok())
+      << deserialize_result.error().to_string();
+
+  auto deserialized = std::move(deserialize_result).value();
+  EXPECT_TRUE(deserialized.strings.empty());
+}
+
 } // namespace
 } // namespace serialization
 } // namespace fory
diff --git a/cpp/fory/serialization/serializer_traits.h 
b/cpp/fory/serialization/serializer_traits.h
index b1846b9fe..7f3ad3d71 100644
--- a/cpp/fory/serialization/serializer_traits.h
+++ b/cpp/fory/serialization/serializer_traits.h
@@ -25,6 +25,7 @@
 #include "fory/type/type.h"
 #include <array>
 #include <deque>
+#include <forward_list>
 #include <list>
 #include <map>
 #include <memory>
@@ -78,6 +79,31 @@ struct is_vector<std::vector<T, Alloc>> : std::true_type {};
 
 template <typename T> inline constexpr bool is_vector_v = is_vector<T>::value;
 
+/// Detect std::list
+template <typename T> struct is_list : std::false_type {};
+
+template <typename T, typename Alloc>
+struct is_list<std::list<T, Alloc>> : std::true_type {};
+
+template <typename T> inline constexpr bool is_list_v = is_list<T>::value;
+
+/// Detect std::deque
+template <typename T> struct is_deque : std::false_type {};
+
+template <typename T, typename Alloc>
+struct is_deque<std::deque<T, Alloc>> : std::true_type {};
+
+template <typename T> inline constexpr bool is_deque_v = is_deque<T>::value;
+
+/// Detect std::forward_list
+template <typename T> struct is_forward_list : std::false_type {};
+
+template <typename T, typename Alloc>
+struct is_forward_list<std::forward_list<T, Alloc>> : std::true_type {};
+
+template <typename T>
+inline constexpr bool is_forward_list_v = is_forward_list<T>::value;
+
 /// Detect std::optional
 template <typename T> struct is_optional : std::false_type {};
 
@@ -241,6 +267,15 @@ struct is_generic_type<std::set<T, Args...>> : 
std::true_type {};
 template <typename T, typename... Args>
 struct is_generic_type<std::unordered_set<T, Args...>> : std::true_type {};
 
+template <typename T, typename Alloc>
+struct is_generic_type<std::list<T, Alloc>> : std::true_type {};
+
+template <typename T, typename Alloc>
+struct is_generic_type<std::deque<T, Alloc>> : std::true_type {};
+
+template <typename T, typename Alloc>
+struct is_generic_type<std::forward_list<T, Alloc>> : std::true_type {};
+
 template <typename... Ts>
 struct is_generic_type<std::tuple<Ts...>> : std::true_type {};
 
@@ -456,6 +491,12 @@ template <typename T> struct TypeIndex<std::deque<T>> {
       fnv1a_64_combine(fnv1a_64("std::deque"), type_index<T>());
 };
 
+// forward_list<T>
+template <typename T> struct TypeIndex<std::forward_list<T>> {
+  static constexpr uint64_t value =
+      fnv1a_64_combine(fnv1a_64("std::forward_list"), type_index<T>());
+};
+
 // set<T>
 template <typename T> struct TypeIndex<std::set<T>> {
   static constexpr uint64_t value =
diff --git a/cpp/fory/serialization/type_resolver.h 
b/cpp/fory/serialization/type_resolver.h
index 987e70a5e..a4a605735 100644
--- a/cpp/fory/serialization/type_resolver.h
+++ b/cpp/fory/serialization/type_resolver.h
@@ -334,6 +334,42 @@ struct FieldTypeBuilder<T, 
std::enable_if_t<is_vector_v<decay_t<T>>>> {
   }
 };
 
+template <typename T>
+struct FieldTypeBuilder<T, std::enable_if_t<is_list_v<decay_t<T>>>> {
+  using List = decay_t<T>;
+  using Element = typename List::value_type;
+  static FieldType build(bool nullable) {
+    FieldType elem = FieldTypeBuilder<Element>::build(false);
+    FieldType ft(to_type_id(TypeId::LIST), nullable);
+    ft.generics.push_back(std::move(elem));
+    return ft;
+  }
+};
+
+template <typename T>
+struct FieldTypeBuilder<T, std::enable_if_t<is_deque_v<decay_t<T>>>> {
+  using Deque = decay_t<T>;
+  using Element = typename Deque::value_type;
+  static FieldType build(bool nullable) {
+    FieldType elem = FieldTypeBuilder<Element>::build(false);
+    FieldType ft(to_type_id(TypeId::LIST), nullable);
+    ft.generics.push_back(std::move(elem));
+    return ft;
+  }
+};
+
+template <typename T>
+struct FieldTypeBuilder<T, std::enable_if_t<is_forward_list_v<decay_t<T>>>> {
+  using FList = decay_t<T>;
+  using Element = typename FList::value_type;
+  static FieldType build(bool nullable) {
+    FieldType elem = FieldTypeBuilder<Element>::build(false);
+    FieldType ft(to_type_id(TypeId::LIST), nullable);
+    ft.generics.push_back(std::move(elem));
+    return ft;
+  }
+};
+
 template <typename T>
 struct FieldTypeBuilder<T, std::enable_if_t<is_set_like_v<decay_t<T>>>> {
   using Set = decay_t<T>;
@@ -409,13 +445,14 @@ struct FieldTypeBuilder<T, 
std::enable_if_t<std::is_enum_v<decay_t<T>>>> {
 
 template <typename T>
 struct FieldTypeBuilder<
-    T,
-    std::enable_if_t<
-        !is_optional_v<decay_t<T>> && !is_shared_ptr_v<decay_t<T>> &&
-        !is_unique_ptr_v<decay_t<T>> && !is_vector_v<decay_t<T>> &&
-        !is_set_like_v<decay_t<T>> && !is_map_like_v<decay_t<T>> &&
-        !is_string_view_v<decay_t<T>> && !is_tuple_v<decay_t<T>> &&
-        !std::is_enum_v<decay_t<T>> && has_serializer_type_id_v<decay_t<T>>>> {
+    T, std::enable_if_t<
+           !is_optional_v<decay_t<T>> && !is_shared_ptr_v<decay_t<T>> &&
+           !is_unique_ptr_v<decay_t<T>> && !is_vector_v<decay_t<T>> &&
+           !is_list_v<decay_t<T>> && !is_deque_v<decay_t<T>> &&
+           !is_forward_list_v<decay_t<T>> && !is_set_like_v<decay_t<T>> &&
+           !is_map_like_v<decay_t<T>> && !is_string_view_v<decay_t<T>> &&
+           !is_tuple_v<decay_t<T>> && !std::is_enum_v<decay_t<T>> &&
+           has_serializer_type_id_v<decay_t<T>>>> {
   using Decayed = decay_t<T>;
   static FieldType build(bool nullable) {
     return FieldType(to_type_id(Serializer<Decayed>::type_id), nullable);


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]


Reply via email to