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 5521c3130 feat(c++): make fory enum/nuion macro in user namespace 
(#3200)
5521c3130 is described below

commit 5521c3130d5f7193d2fe5ce32923e3ced591f1d7
Author: Shawn Yang <[email protected]>
AuthorDate: Mon Jan 26 11:06:17 2026 +0800

    feat(c++): make fory enum/nuion macro in user namespace (#3200)
    
    ## Why?
    
    Allow C++ enum/union/field metadata macros to live in user namespaces
    (no forced `fory::` specialization) while keeping
    registration/serialization behavior consistent.
    
    ## What does this PR do?
    
    - Switch C++ enum/field/union metadata to ADL-based descriptors and
    update union serialization to use `UnionInfo` (case ids, metas,
    factories).
    - Adjust C++ codegen to emit namespace-scope macros and clean up
    union/field generation details.
    - Add a namespace macro test and wire it into C++ serialization
    build/test targets.
    - Update C++ docs to clarify scope rules (`FORY_ENUM` at namespace
    scope, `FORY_STRUCT` in public scope).
    - Set `FORY_JAVASCRIPT_JAVA_CI=0` in the JavaScript xlang CI job.
    
    ## Related issues
    
    #3099
    #2906
    #1017
    
    ## Does this PR introduce any user-facing change?
    
    C++ macro placement rules are now enforced via ADL-based descriptors;
    docs call out the required scope for `FORY_ENUM`/`FORY_STRUCT`.
    
    - [ ] Does this PR introduce any public API change?
    - [ ] Does this PR introduce any binary protocol compatibility change?
    
    ## Benchmark
---
 .github/workflows/ci.yml                       |   2 +-
 compiler/fory_compiler/generators/cpp.py       |  19 +-
 cpp/README.md                                  |   1 +
 cpp/fory/meta/enum_info.h                      |  65 +++-
 cpp/fory/meta/enum_info_test.cc                |   4 +-
 cpp/fory/meta/field.h                          | 301 ++++++++++++++---
 cpp/fory/meta/field_test.cc                    |  21 +-
 cpp/fory/serialization/BUILD                   |  10 +
 cpp/fory/serialization/CMakeLists.txt          |   4 +
 cpp/fory/serialization/namespace_macro_test.cc | 196 +++++++++++
 cpp/fory/serialization/struct_serializer.h     |   5 +-
 cpp/fory/serialization/union_serializer.h      | 449 +++++++++++++++++--------
 docs/guide/cpp/index.md                        |   9 +-
 docs/guide/cpp/supported-types.md              |   1 +
 docs/guide/cpp/type-registration.md            |   1 +
 15 files changed, 855 insertions(+), 233 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 14520202f..2a17f4099 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -458,7 +458,7 @@ jobs:
             ${{ runner.os }}-maven-
       - name: Run JavaScript Xlang Test
         env:
-          # FORY_JAVASCRIPT_JAVA_CI: "1"
+          FORY_JAVASCRIPT_JAVA_CI: "0"
         run: |
           cd javascript
           npm install
diff --git a/compiler/fory_compiler/generators/cpp.py 
b/compiler/fory_compiler/generators/cpp.py
index a966d2d81..ffe3b9bf3 100644
--- a/compiler/fory_compiler/generators/cpp.py
+++ b/compiler/fory_compiler/generators/cpp.py
@@ -191,10 +191,6 @@ class CppGenerator(BaseGenerator):
             )
             lines.append("")
 
-        if namespace:
-            lines.append(f"}} // namespace {namespace}")
-            lines.append("")
-
         if union_macros:
             lines.extend(union_macros)
             lines.append("")
@@ -207,10 +203,6 @@ class CppGenerator(BaseGenerator):
             lines.extend(enum_macros)
             lines.append("")
 
-        if namespace:
-            lines.append(f"namespace {namespace} {{")
-            lines.append("")
-
         # Generate registration function (after FORY_STRUCT/FORY_ENUM)
         lines.extend(self.generate_registration())
         lines.append("")
@@ -548,13 +540,9 @@ class CppGenerator(BaseGenerator):
         lineage = parent_stack + [message]
         body_indent = f"{indent}  "
         field_indent = f"{indent}    "
-
         lines.append(f"{indent}class {class_name} final {{")
         lines.append(f"{body_indent}public:")
         if message.fields:
-            lines.append(
-                f"{body_indent}  friend struct 
::fory::detail::ForyFieldConfigImpl<{class_name}>;"
-            )
             lines.append("")
 
         for nested_enum in message.nested_enums:
@@ -753,9 +741,9 @@ class CppGenerator(BaseGenerator):
         lines.append(f"{body_indent}  }}")
         lines.append("")
 
+        lines.append(f"{body_indent}private:")
         lines.append(f"{body_indent}  {variant_type} value_;")
         lines.append("")
-        lines.append(f"{body_indent}private:")
         lines.append(f"{body_indent}  template <class T, class... Args>")
         lines.append(
             f"{body_indent}  explicit {class_name}(std::in_place_type_t<T> 
tag, Args&&... args)"
@@ -777,8 +765,8 @@ class CppGenerator(BaseGenerator):
             return []
 
         lines: List[str] = []
-        union_type = self.get_namespaced_type_name(union.name, parent_stack)
         if len(union.fields) <= 16:
+            union_type = self.get_namespaced_type_name(union.name, 
parent_stack)
             lines.append(f"FORY_UNION({union_type},")
             for index, field in enumerate(union.fields):
                 case_type = self.generate_namespaced_type(
@@ -792,10 +780,11 @@ class CppGenerator(BaseGenerator):
                 case_ctor = self.to_snake_case(field.name)
                 meta = self.get_union_field_meta(field)
                 suffix = "," if index + 1 < len(union.fields) else ""
-                lines.append(f"    ({case_type}, {case_ctor}, {meta}){suffix}")
+                lines.append(f"  ({case_type}, {case_ctor}, {meta}){suffix}")
             lines.append(");")
             return lines
 
+        union_type = self.get_namespaced_type_name(union.name, parent_stack)
         case_ids = ", ".join(str(field.number) for field in union.fields)
         lines.append(f"FORY_UNION_IDS({union_type}, {case_ids});")
         for field in union.fields:
diff --git a/cpp/README.md b/cpp/README.md
index 0680cc3c9..60a084398 100644
--- a/cpp/README.md
+++ b/cpp/README.md
@@ -232,6 +232,7 @@ enum class Color { Red, Green, Blue };
 // Non-continuous enum - needs FORY_ENUM
 enum class LegacyStatus { Active = 1, Inactive = 5, Pending = 10 };
 FORY_ENUM(LegacyStatus, Active, Inactive, Pending);
+// FORY_ENUM must be defined at namespace scope.
 
 struct Item {
   std::string name;
diff --git a/cpp/fory/meta/enum_info.h b/cpp/fory/meta/enum_info.h
index fe9d9c5d3..736fe63a1 100644
--- a/cpp/fory/meta/enum_info.h
+++ b/cpp/fory/meta/enum_info.h
@@ -19,6 +19,7 @@
 
 #pragma once
 
+#include "fory/meta/field_info.h"
 #include "fory/meta/preprocessor.h"
 #include <array>
 #include <cstddef>
@@ -30,15 +31,30 @@
 namespace fory {
 namespace meta {
 
+namespace detail {
+
+template <typename Enum>
+using AdlEnumInfoDescriptor =
+    decltype(ForyEnumInfo(std::declval<Identity<Enum>>()));
+
+template <typename Enum, typename = void>
+struct HasAdlEnumInfo : std::false_type {};
+
+template <typename Enum>
+struct HasAdlEnumInfo<Enum, std::void_t<AdlEnumInfoDescriptor<Enum>>>
+    : std::true_type {};
+
+} // namespace detail
+
 /// Compile-time metadata for enums registered with FORY_ENUM.
 /// Default implementation assumes no metadata is available.
-template <typename Enum> struct EnumInfo {
+template <typename Enum, typename Enable = void> struct EnumInfo {
   using EnumType = Enum;
   static constexpr bool defined = false;
   static constexpr std::size_t size = 0;
 
-  static constexpr std::array<EnumType, 0> values = {};
-  static constexpr std::array<std::string_view, 0> names = {};
+  static inline constexpr std::array<EnumType, 0> values = {};
+  static inline constexpr std::array<std::string_view, 0> names = {};
 
   static constexpr bool contains(EnumType) { return false; }
 
@@ -52,11 +68,33 @@ template <typename Enum> struct EnumInfo {
 };
 
 template <typename Enum>
-constexpr std::array<typename EnumInfo<Enum>::EnumType, 0>
-    EnumInfo<Enum>::values;
+struct EnumInfo<Enum, std::enable_if_t<detail::HasAdlEnumInfo<Enum>::value>> {
+  using Descriptor = detail::AdlEnumInfoDescriptor<Enum>;
+  using EnumType = Enum;
+  static constexpr bool defined = Descriptor::defined;
+  static constexpr std::size_t size = Descriptor::size;
 
-template <typename Enum>
-constexpr std::array<std::string_view, 0> EnumInfo<Enum>::names;
+  static inline constexpr std::array<EnumType, size> values =
+      Descriptor::values;
+  static inline constexpr std::array<std::string_view, size> names =
+      Descriptor::names;
+
+  static constexpr bool contains(EnumType value) {
+    return Descriptor::contains(value);
+  }
+
+  static constexpr std::size_t ordinal(EnumType value) {
+    return Descriptor::ordinal(value);
+  }
+
+  static constexpr EnumType value_at(std::size_t index) {
+    return Descriptor::value_at(index);
+  }
+
+  static constexpr std::string_view name(EnumType value) {
+    return Descriptor::name(value);
+  }
+};
 
 /// Metadata helpers that map enums to contiguous ordinals when metadata is
 /// available, falling back to naive casts otherwise. All functions return
@@ -121,10 +159,14 @@ struct EnumMetadata<Enum, 
std::enable_if_t<EnumInfo<Enum>::defined>> {
 #define FORY_INTERNAL_ENUM_NAME_ENTRY(EnumType, value)                         
\
   std::string_view(FORY_PP_STRINGIFY(EnumType::value)),
 
+#define FORY_ENUM_DESCRIPTOR_NAME(line)                                        
\
+  FORY_PP_CONCAT(ForyEnumInfoDescriptor_, line)
+
 #define FORY_INTERNAL_ENUM_DEFINE(EnumType, ...)                               
\
-  namespace fory {                                                             
\
-  namespace meta {                                                             
\
-  template <> struct EnumInfo<EnumType> {                                      
\
+  FORY_INTERNAL_ENUM_DEFINE_IMPL(__LINE__, EnumType, __VA_ARGS__)
+
+#define FORY_INTERNAL_ENUM_DEFINE_IMPL(line, EnumType, ...)                    
\
+  struct FORY_ENUM_DESCRIPTOR_NAME(line) {                                     
\
     using Enum = EnumType;                                                     
\
     static constexpr bool defined = true;                                      
\
     static constexpr std::size_t size = FORY_PP_NARG(__VA_ARGS__);             
\
@@ -166,7 +208,8 @@ struct EnumMetadata<Enum, 
std::enable_if_t<EnumInfo<Enum>::defined>> {
       return std::string_view();                                               
\
     }                                                                          
\
   };                                                                           
\
-  }                                                                            
\
+  constexpr auto ForyEnumInfo(::fory::meta::Identity<EnumType>) {              
\
+    return FORY_ENUM_DESCRIPTOR_NAME(line){};                                  
\
   }                                                                            
\
   static_assert(true)
 
diff --git a/cpp/fory/meta/enum_info_test.cc b/cpp/fory/meta/enum_info_test.cc
index ea02015a3..eca161afa 100644
--- a/cpp/fory/meta/enum_info_test.cc
+++ b/cpp/fory/meta/enum_info_test.cc
@@ -38,11 +38,11 @@ enum LegacyEnum : int32_t {
   LEGACY_THIRD = 25
 };
 
-} // namespace fory
-
 FORY_ENUM(::fory::RegisteredEnum, FIRST, SECOND, THIRD);
 FORY_ENUM(::fory::LegacyEnum, LEGACY_FIRST, LEGACY_SECOND, LEGACY_THIRD);
 
+} // namespace fory
+
 namespace fory {
 namespace test {
 
diff --git a/cpp/fory/meta/field.h b/cpp/fory/meta/field.h
index 91c613a3d..3998ab186 100644
--- a/cpp/fory/meta/field.h
+++ b/cpp/fory/meta/field.h
@@ -19,6 +19,7 @@
 
 #pragma once
 
+#include "fory/meta/field_info.h"
 #include "fory/meta/preprocessor.h"
 #include "fory/type/type.h"
 #include <array>
@@ -69,6 +70,36 @@ namespace detail {
 // Type Traits for Smart Pointers and Optional
 // ============================================================================
 
+template <typename T>
+using FieldInfo = decltype(::fory::meta::ForyFieldInfo(std::declval<T>()));
+
+inline constexpr size_t kInvalidFieldIndex = static_cast<size_t>(-1);
+
+template <typename T> constexpr size_t FieldIndex(std::string_view name) {
+  constexpr auto names = FieldInfo<T>::Names;
+  for (size_t i = 0; i < names.size(); ++i) {
+    if (names[i] == name) {
+      return i;
+    }
+  }
+  return kInvalidFieldIndex;
+}
+
+template <typename T, size_t Index, typename Enable = void> struct FieldTypeAt;
+
+template <typename T, size_t Index>
+struct FieldTypeAt<T, Index, std::enable_if_t<Index != kInvalidFieldIndex>> {
+  using PtrsType = typename FieldInfo<T>::PtrsType;
+  using PtrT = std::tuple_element_t<Index, PtrsType>;
+  using type = ::fory::meta::RemoveMemberPointerCVRefT<PtrT>;
+};
+
+template <typename T, size_t Index>
+struct FieldTypeAt<T, Index, std::enable_if_t<Index == kInvalidFieldIndex>> {
+  static_assert(Index != kInvalidFieldIndex,
+                "Unknown field name in FORY_FIELD_TAGS");
+};
+
 template <typename T> struct is_shared_ptr : std::false_type {};
 
 template <typename T>
@@ -146,13 +177,66 @@ struct FieldTagEntry {
   static constexpr int dynamic_value = Dynamic;
 };
 
-/// Default: no field tags defined for type T
+struct FieldTagEntryWithName {
+  const char *name;
+  int16_t id;
+  bool is_nullable;
+  bool track_ref;
+  int dynamic_value;
+};
+
+template <typename Entry>
+constexpr FieldTagEntryWithName make_field_tag_entry(const char *name) {
+  return FieldTagEntryWithName{name, Entry::id, Entry::is_nullable,
+                               Entry::track_ref, Entry::dynamic_value};
+}
+
+/// Default: no field tags defined for type T (legacy specialization path)
 template <typename T> struct ForyFieldTagsImpl {
   static constexpr bool has_tags = false;
 };
 
 template <typename T>
-inline constexpr bool has_field_tags_v = ForyFieldTagsImpl<T>::has_tags;
+using AdlFieldTagsDescriptor =
+    decltype(ForyFieldTags(std::declval<meta::Identity<T>>()));
+
+template <typename T, typename = void>
+struct HasAdlFieldTags : std::false_type {};
+
+template <typename T>
+struct HasAdlFieldTags<T, std::void_t<AdlFieldTagsDescriptor<T>>>
+    : std::true_type {};
+
+template <typename T, typename Enable = void> struct FieldTagsInfo {
+  static constexpr bool has_tags = false;
+  static constexpr size_t field_count = 0;
+  static inline constexpr auto entries = std::tuple<>{};
+  using Entries = std::decay_t<decltype(entries)>;
+  static constexpr bool use_index = true;
+};
+
+template <typename T>
+struct FieldTagsInfo<T, std::enable_if_t<HasAdlFieldTags<T>::value>> {
+  using Descriptor = AdlFieldTagsDescriptor<T>;
+  static constexpr bool has_tags = Descriptor::has_tags;
+  static inline constexpr auto entries = Descriptor::entries;
+  using Entries = std::decay_t<decltype(entries)>;
+  static constexpr size_t field_count = std::tuple_size_v<Entries>;
+  static constexpr bool use_index = false;
+};
+
+template <typename T>
+struct FieldTagsInfo<T, std::enable_if_t<!HasAdlFieldTags<T>::value &&
+                                         ForyFieldTagsImpl<T>::has_tags>> {
+  static constexpr bool has_tags = true;
+  static constexpr size_t field_count = ForyFieldTagsImpl<T>::field_count;
+  using Entries = typename ForyFieldTagsImpl<T>::Entries;
+  static inline constexpr auto entries = Entries{};
+  static constexpr bool use_index = true;
+};
+
+template <typename T>
+inline constexpr bool has_field_tags_v = FieldTagsInfo<T>::has_tags;
 
 } // namespace detail
 
@@ -280,23 +364,20 @@ constexpr FieldMeta apply_tags(FieldMeta base, Tags... 
tags) {
 }
 
 // ============================================================================
-// FieldEntry - Binds Member Pointer to Config for Compile-Time Verification
+// FieldEntry - Stores Field Configuration Metadata
 // ============================================================================
 
-/// Field entry that stores member pointer (for verification) + configuration
-template <typename T, typename M> struct FieldEntry {
-  M T::*ptr;        // Member pointer - compile-time field verification
+/// Field entry that stores name and configuration metadata
+struct FieldEntry {
   const char *name; // Field name for debugging
   FieldMeta meta;   // Field configuration
 
-  constexpr FieldEntry(M T::*p, const char *n, FieldMeta m)
-      : ptr(p), name(n), meta(m) {}
+  constexpr FieldEntry(const char *n, FieldMeta m) : name(n), meta(m) {}
 };
 
-/// Create a FieldEntry with automatic type deduction
-template <typename T, typename M>
-constexpr auto make_field_entry(M T::*ptr, const char *name, FieldMeta meta) {
-  return FieldEntry<T, M>{ptr, name, meta};
+/// Create a FieldEntry
+constexpr auto make_field_entry(const char *name, FieldMeta meta) {
+  return FieldEntry{name, meta};
 }
 
 /// Default: no field config defined for type T
@@ -305,9 +386,43 @@ template <typename T> struct ForyFieldConfigImpl {
 };
 
 template <typename T>
-inline constexpr bool has_field_config_v = ForyFieldConfigImpl<T>::has_config;
+using AdlFieldConfigDescriptor =
+    decltype(ForyFieldConfig(std::declval<meta::Identity<T>>()));
+
+template <typename T, typename = void>
+struct HasAdlFieldConfig : std::false_type {};
+
+template <typename T>
+struct HasAdlFieldConfig<T, std::void_t<AdlFieldConfigDescriptor<T>>>
+    : std::true_type {};
+
+template <typename T, typename Enable = void> struct FieldConfigInfo {
+  static constexpr bool has_config = false;
+  static constexpr size_t field_count = 0;
+  static inline constexpr auto entries = std::tuple<>{};
+};
+
+template <typename T>
+struct FieldConfigInfo<T, std::enable_if_t<HasAdlFieldConfig<T>::value>> {
+  using Descriptor = AdlFieldConfigDescriptor<T>;
+  static constexpr bool has_config = Descriptor::has_config;
+  static constexpr size_t field_count = Descriptor::field_count;
+  static inline constexpr auto entries = Descriptor::entries;
+};
+
+template <typename T>
+struct FieldConfigInfo<T,
+                       std::enable_if_t<!HasAdlFieldConfig<T>::value &&
+                                        ForyFieldConfigImpl<T>::has_config>> {
+  static constexpr bool has_config = true;
+  static constexpr size_t field_count = ForyFieldConfigImpl<T>::field_count;
+  static inline constexpr auto entries = ForyFieldConfigImpl<T>::entries;
+};
+
+template <typename T>
+inline constexpr bool has_field_config_v = FieldConfigInfo<T>::has_config;
 
-/// Helper to get field encoding from ForyFieldConfigImpl
+/// Helper to get field encoding from FieldConfigInfo
 template <typename T, size_t Index, typename = void>
 struct GetFieldConfigEntry {
   static constexpr Encoding encoding = Encoding::Default;
@@ -317,27 +432,39 @@ struct GetFieldConfigEntry {
   static constexpr int dynamic_value = -1; // AUTO
   static constexpr bool compress = true;
   static constexpr int16_t type_id_override = -1;
+  static constexpr bool has_entry = false;
 };
 
 template <typename T, size_t Index>
-struct GetFieldConfigEntry<
-    T, Index,
-    std::enable_if_t<ForyFieldConfigImpl<T>::has_config &&
-                     (Index < ForyFieldConfigImpl<T>::field_count)>> {
+struct GetFieldConfigEntry<T, Index,
+                           std::enable_if_t<FieldConfigInfo<T>::has_config>> {
 private:
-  static constexpr auto get_entry() {
-    return std::get<Index>(ForyFieldConfigImpl<T>::entries);
+  static constexpr std::string_view field_name = FieldInfo<T>::Names[Index];
+
+  template <size_t I = 0> static constexpr FieldEntry find_entry() {
+    if constexpr (I >=
+                  std::tuple_size_v<
+                      std::decay_t<decltype(FieldConfigInfo<T>::entries)>>) {
+      return FieldEntry{"", FieldMeta{}};
+    } else {
+      constexpr auto entry = std::get<I>(FieldConfigInfo<T>::entries);
+      if (std::string_view{entry.name} == field_name) {
+        return entry;
+      }
+      return find_entry<I + 1>();
+    }
   }
 
 public:
-  static constexpr Encoding encoding = get_entry().meta.encoding_;
-  static constexpr int16_t id = get_entry().meta.id_;
-  static constexpr bool nullable = get_entry().meta.nullable_;
-  static constexpr bool ref = get_entry().meta.ref_;
-  static constexpr int dynamic_value = get_entry().meta.dynamic_;
-  static constexpr bool compress = get_entry().meta.compress_;
-  static constexpr int16_t type_id_override =
-      get_entry().meta.type_id_override_;
+  static constexpr FieldEntry entry = find_entry<>();
+  static constexpr Encoding encoding = entry.meta.encoding_;
+  static constexpr int16_t id = entry.meta.id_;
+  static constexpr bool nullable = entry.meta.nullable_;
+  static constexpr bool ref = entry.meta.ref_;
+  static constexpr int dynamic_value = entry.meta.dynamic_;
+  static constexpr bool compress = entry.meta.compress_;
+  static constexpr int16_t type_id_override = entry.meta.type_id_override_;
+  static constexpr bool has_entry = entry.name[0] != '\0';
 };
 
 } // namespace detail
@@ -597,25 +724,56 @@ struct ParseFieldTagEntry {
   using type = FieldTagEntry<Id, is_nullable, track_ref, dynamic_value>;
 };
 
-/// Get field tag entry by index from ForyFieldTagsImpl
+/// Get field tag entry by index from FieldTagsInfo
 template <typename T, size_t Index, typename = void> struct GetFieldTagEntry {
   static constexpr int16_t id = -1;
   static constexpr bool is_nullable = false;
   static constexpr bool track_ref = false;
   static constexpr int dynamic_value = -1; // AUTO
+  static constexpr bool has_entry = false;
 };
 
 template <typename T, size_t Index>
 struct GetFieldTagEntry<
     T, Index,
-    std::enable_if_t<ForyFieldTagsImpl<T>::has_tags &&
-                     (Index < ForyFieldTagsImpl<T>::field_count)>> {
-  using Entry =
-      std::tuple_element_t<Index, typename ForyFieldTagsImpl<T>::Entries>;
+    std::enable_if_t<FieldTagsInfo<T>::has_tags &&
+                     (Index < FieldTagsInfo<T>::field_count) &&
+                     FieldTagsInfo<T>::use_index>> {
+  using Entry = std::tuple_element_t<Index, typename 
FieldTagsInfo<T>::Entries>;
   static constexpr int16_t id = Entry::id;
   static constexpr bool is_nullable = Entry::is_nullable;
   static constexpr bool track_ref = Entry::track_ref;
   static constexpr int dynamic_value = Entry::dynamic_value;
+  static constexpr bool has_entry = true;
+};
+
+template <typename T, size_t Index>
+struct GetFieldTagEntry<T, Index,
+                        std::enable_if_t<FieldTagsInfo<T>::has_tags &&
+                                         !FieldTagsInfo<T>::use_index>> {
+private:
+  static constexpr std::string_view field_name = FieldInfo<T>::Names[Index];
+
+  template <size_t I = 0> static constexpr FieldTagEntryWithName find_entry() {
+    if constexpr (I >= std::tuple_size_v<typename FieldTagsInfo<T>::Entries>) {
+      return FieldTagEntryWithName{"", -1, false, false, -1};
+    } else {
+      constexpr auto entry = std::get<I>(FieldTagsInfo<T>::entries);
+      if (std::string_view{entry.name} == field_name) {
+        return entry;
+      }
+      return find_entry<I + 1>();
+    }
+  }
+
+  static constexpr FieldTagEntryWithName entry = find_entry<>();
+
+public:
+  static constexpr int16_t id = entry.id;
+  static constexpr bool is_nullable = entry.is_nullable;
+  static constexpr bool track_ref = entry.track_ref;
+  static constexpr int dynamic_value = entry.dynamic_value;
+  static constexpr bool has_entry = entry.name[0] != '\0';
 };
 
 } // namespace detail
@@ -630,6 +788,10 @@ struct GetFieldTagEntry<
 #define FORY_FT_FIELD(tuple) FORY_FT_FIELD_IMPL tuple
 #define FORY_FT_FIELD_IMPL(field, ...) field
 
+// Stringify field name
+#define FORY_FT_STRINGIFY(x) FORY_FT_STRINGIFY_I(x)
+#define FORY_FT_STRINGIFY_I(x) #x
+
 #define FORY_FT_ID(tuple) FORY_FT_ID_IMPL tuple
 #define FORY_FT_ID_IMPL(field, id, ...) id
 
@@ -656,35 +818,60 @@ struct GetFieldTagEntry<
 #define FORY_FT_MAKE_ENTRY_II(Type, tuple, size)                               
\
   FORY_FT_MAKE_ENTRY_##size(Type, tuple)
 
+#define FORY_FT_FIELD_INDEX(Type, tuple)                                       
\
+  ::fory::detail::FieldIndex<Type>(                                            
\
+      std::string_view{FORY_FT_STRINGIFY(FORY_FT_FIELD(tuple))})
+
+#define FORY_FT_FIELD_TYPE(Type, tuple)                                        
\
+  typename ::fory::detail::FieldTypeAt<Type,                                   
\
+                                       FORY_FT_FIELD_INDEX(Type, tuple)>::type
+
 #define FORY_FT_MAKE_ENTRY_2(Type, tuple)                                      
\
-  typename ::fory::detail::ParseFieldTagEntry<                                 
\
-      decltype(std::declval<Type>().FORY_FT_FIELD(tuple)),                     
\
-      FORY_FT_ID(tuple)>::type
+  ::fory::detail::make_field_tag_entry<                                        
\
+      typename ::fory::detail::ParseFieldTagEntry<                             
\
+          FORY_FT_FIELD_TYPE(Type, tuple), FORY_FT_ID(tuple)>::type>(          
\
+      FORY_FT_STRINGIFY(FORY_FT_FIELD(tuple)))
 
 #define FORY_FT_MAKE_ENTRY_3(Type, tuple)                                      
\
-  typename ::fory::detail::ParseFieldTagEntry<                                 
\
-      decltype(std::declval<Type>().FORY_FT_FIELD(tuple)), FORY_FT_ID(tuple),  
\
-      ::fory::FORY_FT_GET_OPT1(tuple)>::type
+  ::fory::detail::make_field_tag_entry<                                        
\
+      typename ::fory::detail::ParseFieldTagEntry<                             
\
+          FORY_FT_FIELD_TYPE(Type, tuple), FORY_FT_ID(tuple),                  
\
+          ::fory::FORY_FT_GET_OPT1(tuple)>::type>(                             
\
+      FORY_FT_STRINGIFY(FORY_FT_FIELD(tuple)))
 
 #define FORY_FT_MAKE_ENTRY_4(Type, tuple)                                      
\
-  typename ::fory::detail::ParseFieldTagEntry<                                 
\
-      decltype(std::declval<Type>().FORY_FT_FIELD(tuple)), FORY_FT_ID(tuple),  
\
-      ::fory::FORY_FT_GET_OPT1(tuple), ::fory::FORY_FT_GET_OPT2(tuple)>::type
+  ::fory::detail::make_field_tag_entry<                                        
\
+      typename ::fory::detail::ParseFieldTagEntry<                             
\
+          FORY_FT_FIELD_TYPE(Type, tuple), FORY_FT_ID(tuple),                  
\
+          ::fory::FORY_FT_GET_OPT1(tuple),                                     
\
+          ::fory::FORY_FT_GET_OPT2(tuple)>::type>(                             
\
+      FORY_FT_STRINGIFY(FORY_FT_FIELD(tuple)))
 
 #define FORY_FT_MAKE_ENTRY_5(Type, tuple)                                      
\
-  typename ::fory::detail::ParseFieldTagEntry<                                 
\
-      decltype(std::declval<Type>().FORY_FT_FIELD(tuple)), FORY_FT_ID(tuple),  
\
-      ::fory::FORY_FT_GET_OPT1(tuple), ::fory::FORY_FT_GET_OPT2(tuple),        
\
-      ::fory::FORY_FT_GET_OPT3(tuple)>::type
+  ::fory::detail::make_field_tag_entry<                                        
\
+      typename ::fory::detail::ParseFieldTagEntry<                             
\
+          FORY_FT_FIELD_TYPE(Type, tuple), FORY_FT_ID(tuple),                  
\
+          ::fory::FORY_FT_GET_OPT1(tuple), ::fory::FORY_FT_GET_OPT2(tuple),    
\
+          ::fory::FORY_FT_GET_OPT3(tuple)>::type>(                             
\
+      FORY_FT_STRINGIFY(FORY_FT_FIELD(tuple)))
 
 // Main macro: FORY_FIELD_TAGS(Type, (field1, id1), (field2, id2, 
nullable),...)
-// Note: Uses fory::detail:: instead of ::fory::detail:: for GCC compatibility
+#define FORY_FT_DESCRIPTOR_NAME(line)                                          
\
+  FORY_PP_CONCAT(ForyFieldTagsDescriptor_, line)
 #define FORY_FIELD_TAGS(Type, ...)                                             
\
-  template <> struct fory::detail::ForyFieldTagsImpl<Type> {                   
\
+  FORY_FIELD_TAGS_IMPL(__LINE__, Type, __VA_ARGS__)
+#define FORY_FIELD_TAGS_IMPL(line, Type, ...)                                  
\
+  struct FORY_FT_DESCRIPTOR_NAME(line) {                                       
\
     static constexpr bool has_tags = true;                                     
\
-    static constexpr size_t field_count = FORY_PP_NARG(__VA_ARGS__);           
\
-    using Entries = std::tuple<FORY_FT_ENTRIES(Type, __VA_ARGS__)>;            
\
-  }
+    static inline constexpr auto entries =                                     
\
+        std::make_tuple(FORY_FT_ENTRIES(Type, __VA_ARGS__));                   
\
+    using Entries = std::decay_t<decltype(entries)>;                           
\
+    static constexpr size_t field_count = std::tuple_size_v<Entries>;          
\
+  };                                                                           
\
+  constexpr auto ForyFieldTags(::fory::meta::Identity<Type>) {                 
\
+    return FORY_FT_DESCRIPTOR_NAME(line){};                                    
\
+  }                                                                            
\
+  static_assert(true)
 
 // Helper to generate entries tuple content using indirect expansion pattern
 // This ensures FORY_PP_NARG is fully expanded before concatenation
@@ -776,7 +963,7 @@ struct GetFieldTagEntry<
 // Create a FieldEntry with member pointer verification
 #define FORY_FC_MAKE_ENTRY(Type, tuple)                                        
\
   ::fory::detail::make_field_entry(                                            
\
-      &Type::FORY_FC_NAME(tuple), FORY_FC_STRINGIFY(FORY_FC_NAME(tuple)),      
\
+      FORY_FC_STRINGIFY(FORY_FC_NAME(tuple)),                                  
\
       ::fory::detail::normalize_config(FORY_FC_CONFIG(tuple)))
 
 // Generate entries using indirect expansion
@@ -852,11 +1039,17 @@ struct GetFieldTagEntry<
 // Main FORY_FIELD_CONFIG macro
 // Creates a constexpr tuple of FieldEntry objects with member pointer
 // verification. Alias is a token-safe name without '::'.
+#define FORY_FC_DESCRIPTOR_NAME(Alias)                                         
\
+  FORY_PP_CONCAT(ForyFieldConfigDescriptor_, Alias)
 #define FORY_FIELD_CONFIG(Type, Alias, ...)                                    
\
-  template <> struct fory::detail::ForyFieldConfigImpl<Type> {                 
\
+  struct FORY_FC_DESCRIPTOR_NAME(Alias) {                                      
\
     static constexpr bool has_config = true;                                   
\
     static inline constexpr auto entries =                                     
\
         std::make_tuple(FORY_FC_ENTRIES(Type, __VA_ARGS__));                   
\
     static constexpr size_t field_count =                                      
\
         std::tuple_size_v<std::decay_t<decltype(entries)>>;                    
\
-  }
+  };                                                                           
\
+  constexpr auto ForyFieldConfig(::fory::meta::Identity<Type>) {               
\
+    return FORY_FC_DESCRIPTOR_NAME(Alias){};                                   
\
+  }                                                                            
\
+  static_assert(true)
diff --git a/cpp/fory/meta/field_test.cc b/cpp/fory/meta/field_test.cc
index f3976fd05..528800f1b 100644
--- a/cpp/fory/meta/field_test.cc
+++ b/cpp/fory/meta/field_test.cc
@@ -278,7 +278,6 @@ TEST(FieldStruct, FieldInfo) {
 
 // ============================================================================
 // FORY_FIELD_TAGS Macro Tests
-// Must be in global namespace to specialize fory::detail::ForyFieldTagsImpl
 // ============================================================================
 
 namespace field_tags_test {
@@ -310,23 +309,21 @@ struct SingleField {
   FORY_STRUCT(SingleField, value);
 };
 
-} // namespace field_tags_test
-
-// FORY_FIELD_TAGS must be in global namespace
-
-// Define field tags separately (non-invasive)
-FORY_FIELD_TAGS(field_tags_test::Document, (title, 0), // string: non-nullable
-                (version, 1),                          // int: non-nullable
+// Define field tags in the same namespace as the types.
+FORY_FIELD_TAGS(Document, (title, 0),     // string: non-nullable
+                (version, 1),             // int: non-nullable
                 (description, 2),         // optional: inherently nullable
                 (author, 3),              // shared_ptr: non-nullable (default)
                 (reviewer, 4, nullable),  // shared_ptr: nullable
                 (parent, 5, ref),         // shared_ptr: non-nullable, with ref
                 (metadata, 6, nullable)); // unique_ptr: nullable
 
-FORY_FIELD_TAGS(field_tags_test::Node, (name, 0), (left, 1, nullable, ref),
+FORY_FIELD_TAGS(Node, (name, 0), (left, 1, nullable, ref),
                 (right, 2, nullable, ref));
 
-FORY_FIELD_TAGS(field_tags_test::SingleField, (value, 0));
+FORY_FIELD_TAGS(SingleField, (value, 0));
+
+} // namespace field_tags_test
 
 namespace fory {
 namespace test {
@@ -342,7 +339,7 @@ TEST(FieldTags, HasTags) {
 }
 
 TEST(FieldTags, FieldCount) {
-  static_assert(detail::ForyFieldTagsImpl<Document>::field_count == 7);
+  static_assert(detail::FieldTagsInfo<Document>::field_count == 7);
 }
 
 TEST(FieldTags, TagIds) {
@@ -403,7 +400,7 @@ TEST(FieldTags, NullableWithRef) {
 
 TEST(FieldTags, SingleField) {
   static_assert(detail::has_field_tags_v<SingleField> == true);
-  static_assert(detail::ForyFieldTagsImpl<SingleField>::field_count == 1);
+  static_assert(detail::FieldTagsInfo<SingleField>::field_count == 1);
   static_assert(detail::GetFieldTagEntry<SingleField, 0>::id == 0);
   static_assert(detail::GetFieldTagEntry<SingleField, 0>::is_nullable == 
false);
   static_assert(detail::GetFieldTagEntry<SingleField, 0>::track_ref == false);
diff --git a/cpp/fory/serialization/BUILD b/cpp/fory/serialization/BUILD
index 3ff9aec89..34a447018 100644
--- a/cpp/fory/serialization/BUILD
+++ b/cpp/fory/serialization/BUILD
@@ -143,6 +143,16 @@ cc_test(
     ],
 )
 
+cc_test(
+    name = "namespace_macro_test",
+    srcs = ["namespace_macro_test.cc"],
+    deps = [
+        ":fory_serialization",
+        "@googletest//:gtest",
+        "@googletest//:gtest_main",
+    ],
+)
+
 cc_test(
     name = "weak_ptr_serializer_test",
     srcs = ["weak_ptr_serializer_test.cc"],
diff --git a/cpp/fory/serialization/CMakeLists.txt 
b/cpp/fory/serialization/CMakeLists.txt
index 958cbf825..7f5f01d3a 100644
--- a/cpp/fory/serialization/CMakeLists.txt
+++ b/cpp/fory/serialization/CMakeLists.txt
@@ -101,6 +101,10 @@ if(FORY_BUILD_TESTS)
     target_link_libraries(fory_serialization_field_test fory_serialization 
GTest::gtest)
     gtest_discover_tests(fory_serialization_field_test)
 
+    add_executable(fory_serialization_namespace_macro_test 
namespace_macro_test.cc)
+    target_link_libraries(fory_serialization_namespace_macro_test 
fory_serialization GTest::gtest GTest::gtest_main)
+    gtest_discover_tests(fory_serialization_namespace_macro_test)
+
     add_executable(fory_serialization_weak_ptr_test 
weak_ptr_serializer_test.cc)
     target_link_libraries(fory_serialization_weak_ptr_test fory_serialization 
GTest::gtest GTest::gtest_main)
     gtest_discover_tests(fory_serialization_weak_ptr_test)
diff --git a/cpp/fory/serialization/namespace_macro_test.cc 
b/cpp/fory/serialization/namespace_macro_test.cc
new file mode 100644
index 000000000..1676e83a0
--- /dev/null
+++ b/cpp/fory/serialization/namespace_macro_test.cc
@@ -0,0 +1,196 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+#include "fory/meta/enum_info.h"
+#include "fory/serialization/fory.h"
+#include "fory/serialization/struct_serializer.h"
+#include "fory/serialization/union_serializer.h"
+
+#include "gtest/gtest.h"
+
+#include <cstdint>
+#include <optional>
+#include <string>
+#include <utility>
+#include <variant>
+
+namespace macro_test {
+
+enum class LocalEnum { One, Two };
+
+FORY_ENUM(LocalEnum, One, Two);
+
+class Configured final {
+public:
+  Configured() = default;
+  explicit Configured(int32_t id) : id_(id) {}
+
+  bool operator==(const Configured &other) const { return id_ == other.id_; }
+
+private:
+  int32_t id_ = 0;
+
+public:
+  FORY_STRUCT(Configured, id_);
+};
+
+class OptionalHolder final {
+public:
+  OptionalHolder() = default;
+  explicit OptionalHolder(std::optional<std::string> name)
+      : name_(std::move(name)) {}
+
+  bool operator==(const OptionalHolder &other) const {
+    return name_ == other.name_;
+  }
+
+private:
+  std::optional<std::string> name_;
+
+public:
+  FORY_STRUCT(OptionalHolder, name_);
+};
+
+class Choice final {
+public:
+  Choice() = default;
+
+  static Choice text(std::string value) {
+    return Choice(std::in_place_type<std::string>, std::move(value));
+  }
+
+  static Choice number(int32_t value) {
+    return Choice(std::in_place_type<int32_t>, value);
+  }
+
+  uint32_t fory_case_id() const noexcept {
+    if (std::holds_alternative<std::string>(value_)) {
+      return 1;
+    }
+    return 2;
+  }
+
+  template <class Visitor> decltype(auto) visit(Visitor &&vis) const {
+    if (std::holds_alternative<std::string>(value_)) {
+      return std::forward<Visitor>(vis)(std::get<std::string>(value_));
+    }
+    return std::forward<Visitor>(vis)(std::get<int32_t>(value_));
+  }
+
+  bool operator==(const Choice &other) const { return value_ == other.value_; }
+
+private:
+  std::variant<std::string, int32_t> value_;
+
+  template <class T, class... Args>
+  explicit Choice(std::in_place_type_t<T> tag, Args &&...args)
+      : value_(tag, std::forward<Args>(args)...) {}
+};
+
+class Partial final {
+public:
+  Partial() = default;
+  Partial(int32_t id, std::optional<std::string> name, int64_t count)
+      : id_(id), name_(std::move(name)), count_(count) {}
+
+private:
+  int32_t id_ = 0;
+  std::optional<std::string> name_;
+  int64_t count_ = 0;
+
+public:
+  FORY_STRUCT(Partial, id_, name_, count_);
+};
+
+class EnumContainer final {
+public:
+  enum class Kind { Alpha, Beta };
+};
+
+FORY_ENUM(EnumContainer::Kind, Alpha, Beta);
+
+FORY_FIELD_CONFIG(Configured, Configured, (id_, fory::F().id(1).varint()));
+FORY_FIELD_TAGS(OptionalHolder, (name_, 1));
+FORY_FIELD_CONFIG(Partial, Partial, (count_, fory::F().id(7).varint()));
+FORY_FIELD_TAGS(Partial, (id_, 5));
+
+FORY_UNION(Choice, (std::string, text, fory::F(1)),
+           (int32_t, number, fory::F(2).varint()));
+
+} // namespace macro_test
+
+namespace fory {
+namespace serialization {
+namespace test {
+
+TEST(NamespaceMacros, FieldConfigAndTagsInNamespace) {
+  static_assert(::fory::detail::has_field_config_v<macro_test::Configured>);
+  static_assert(
+      ::fory::detail::GetFieldConfigEntry<macro_test::Configured, 0>::id == 1);
+  static_assert(::fory::detail::GetFieldConfigEntry<macro_test::Configured,
+                                                    0>::encoding ==
+                ::fory::Encoding::Varint);
+
+  static_assert(::fory::detail::has_field_tags_v<macro_test::OptionalHolder>);
+  static_assert(::fory::detail::GetFieldTagEntry<macro_test::OptionalHolder,
+                                                 0>::is_nullable);
+
+  static_assert(::fory::detail::GetFieldTagEntry<macro_test::Partial, 0>::id ==
+                5);
+  static_assert(
+      !::fory::detail::GetFieldTagEntry<macro_test::Partial, 1>::has_entry);
+  static_assert(::fory::serialization::detail::CompileTimeFieldHelpers<
+                macro_test::Partial>::field_nullable<1>());
+  static_assert(
+      ::fory::detail::GetFieldConfigEntry<macro_test::Partial, 2>::id == 7);
+  static_assert(
+      ::fory::detail::GetFieldConfigEntry<macro_test::Partial, 0>::id == -1);
+}
+
+TEST(NamespaceMacros, UnionInNamespace) {
+  auto fory = Fory::builder().xlang(true).track_ref(false).build();
+  ASSERT_TRUE(fory.register_union<macro_test::Choice>(1001).ok());
+
+  auto bytes = fory.serialize(macro_test::Choice::text("hello"));
+  ASSERT_TRUE(bytes.ok()) << bytes.error().to_string();
+
+  auto decoded =
+      fory.deserialize<macro_test::Choice>(bytes->data(), bytes->size());
+  ASSERT_TRUE(decoded.ok()) << decoded.error().to_string();
+  EXPECT_EQ(macro_test::Choice::text("hello"), decoded.value());
+}
+
+TEST(NamespaceMacros, EnumInAndOutOfClass) {
+  static_assert(::fory::meta::EnumInfo<macro_test::LocalEnum>::defined);
+  static_assert(::fory::meta::EnumInfo<macro_test::LocalEnum>::size == 2);
+  static_assert(::fory::meta::EnumInfo<macro_test::LocalEnum>::name(
+                    macro_test::LocalEnum::One) == "LocalEnum::One");
+
+  static_assert(
+      ::fory::meta::EnumInfo<macro_test::EnumContainer::Kind>::defined);
+  static_assert(::fory::meta::EnumInfo<macro_test::EnumContainer::Kind>::size 
==
+                2);
+  static_assert(::fory::meta::EnumInfo<macro_test::EnumContainer::Kind>::name(
+                    macro_test::EnumContainer::Kind::Alpha) ==
+                "EnumContainer::Kind::Alpha");
+}
+
+} // namespace test
+} // namespace serialization
+} // namespace fory
diff --git a/cpp/fory/serialization/struct_serializer.h 
b/cpp/fory/serialization/struct_serializer.h
index efa396e62..9276542c0 100644
--- a/cpp/fory/serialization/struct_serializer.h
+++ b/cpp/fory/serialization/struct_serializer.h
@@ -547,7 +547,10 @@ template <typename T> struct CompileTimeFieldHelpers {
       }
       // Else if FORY_FIELD_TAGS is defined, use that metadata
       else if constexpr (::fory::detail::has_field_tags_v<T>) {
-        return ::fory::detail::GetFieldTagEntry<T, Index>::is_nullable;
+        if constexpr (::fory::detail::GetFieldTagEntry<T, Index>::has_entry) {
+          return ::fory::detail::GetFieldTagEntry<T, Index>::is_nullable;
+        }
+        return field_is_nullable_v<RawFieldType>;
       }
       // For non-wrapped types, use xlang defaults:
       // Only std::optional is nullable (field_is_nullable_v returns true for
diff --git a/cpp/fory/serialization/union_serializer.h 
b/cpp/fory/serialization/union_serializer.h
index 3ebbb287f..779f0eb63 100644
--- a/cpp/fory/serialization/union_serializer.h
+++ b/cpp/fory/serialization/union_serializer.h
@@ -28,8 +28,10 @@
 #include "fory/type/type.h"
 #include "fory/util/error.h"
 
+#include <array>
 #include <cstddef>
 #include <optional>
+#include <tuple>
 #include <type_traits>
 #include <typeindex>
 #include <utility>
@@ -37,7 +39,7 @@
 namespace fory {
 namespace serialization {
 
-// Union metadata specializations generated by compiler.
+// Union metadata specializations generated by compiler (legacy).
 template <typename T, typename Enable = void> struct UnionCaseIds;
 template <typename T, uint32_t CaseId> struct UnionCaseMeta;
 
@@ -51,7 +53,112 @@ struct has_union_case_ids<T, 
std::void_t<decltype(UnionCaseIds<T>::case_count)>>
     : std::true_type {};
 
 template <typename T>
-inline constexpr bool is_union_type_v = has_union_case_ids<T>::value;
+using AdlUnionInfoDescriptor =
+    decltype(ForyUnionInfo(std::declval<::fory::meta::Identity<T>>()));
+
+template <typename T, typename = void>
+struct has_adl_union_info : std::false_type {};
+
+template <typename T>
+struct has_adl_union_info<T, std::void_t<AdlUnionInfoDescriptor<T>>>
+    : std::true_type {};
+
+template <typename T>
+using AdlUnionCaseIdsDescriptor =
+    decltype(ForyUnionCaseIds(std::declval<::fory::meta::Identity<T>>()));
+
+template <typename T, typename = void>
+struct has_adl_union_case_ids : std::false_type {};
+
+template <typename T>
+struct has_adl_union_case_ids<T, std::void_t<AdlUnionCaseIdsDescriptor<T>>>
+    : std::true_type {};
+
+template <typename T, uint32_t CaseId>
+using AdlUnionCaseMetaDescriptor =
+    decltype(ForyUnionCaseMeta(std::declval<::fory::meta::Identity<T>>(),
+                               std::integral_constant<uint32_t, CaseId>{}));
+
+template <typename T>
+inline constexpr bool is_union_type_v =
+    has_union_case_ids<T>::value || has_adl_union_info<T>::value ||
+    has_adl_union_case_ids<T>::value;
+
+template <typename T, typename Enable = void> struct UnionInfo;
+
+template <typename T>
+struct UnionInfo<T, std::enable_if_t<has_adl_union_info<T>::value>> {
+  using Descriptor = AdlUnionInfoDescriptor<T>;
+  static constexpr size_t case_count = Descriptor::case_count;
+
+  template <size_t Index> struct CaseId {
+    static constexpr uint32_t value = Descriptor::case_ids[Index];
+  };
+
+  template <size_t Index> struct Meta {
+    static constexpr ::fory::FieldMeta value = Descriptor::case_metas[Index];
+  };
+
+  template <size_t Index>
+  using CaseT = std::tuple_element_t<Index, typename Descriptor::CaseTypes>;
+
+  template <size_t Index> static inline T make(CaseT<Index> value) {
+    auto maker = std::get<Index>(Descriptor::factories);
+    return maker(std::move(value));
+  }
+};
+
+template <typename T>
+struct UnionInfo<T, std::enable_if_t<!has_adl_union_info<T>::value &&
+                                     has_adl_union_case_ids<T>::value>> {
+  using IdsDescriptor = AdlUnionCaseIdsDescriptor<T>;
+  static constexpr size_t case_count = IdsDescriptor::case_count;
+
+  template <size_t Index> struct CaseId {
+    static constexpr uint32_t value = IdsDescriptor::case_ids[Index];
+  };
+
+  template <size_t Index> struct CaseDescriptor {
+    using Descriptor = AdlUnionCaseMetaDescriptor<T, CaseId<Index>::value>;
+    using CaseT = typename Descriptor::CaseT;
+    static constexpr ::fory::FieldMeta meta = Descriptor::meta;
+    static inline T make(CaseT value) {
+      return Descriptor::make(std::move(value));
+    }
+  };
+
+  template <size_t Index> struct Meta {
+    static constexpr ::fory::FieldMeta value = CaseDescriptor<Index>::meta;
+  };
+
+  template <size_t Index> using CaseT = typename CaseDescriptor<Index>::CaseT;
+
+  template <size_t Index> static inline T make(CaseT<Index> value) {
+    return CaseDescriptor<Index>::make(std::move(value));
+  }
+};
+
+template <typename T>
+struct UnionInfo<T, std::enable_if_t<!has_adl_union_info<T>::value &&
+                                     !has_adl_union_case_ids<T>::value>> {
+  static constexpr size_t case_count = UnionCaseIds<T>::case_count;
+
+  template <size_t Index> struct CaseId {
+    static constexpr uint32_t value = UnionCaseIds<T>::case_ids[Index];
+  };
+
+  template <size_t Index> struct Meta {
+    static constexpr ::fory::FieldMeta value =
+        UnionCaseMeta<T, CaseId<Index>::value>::meta;
+  };
+
+  template <size_t Index>
+  using CaseT = typename UnionCaseMeta<T, CaseId<Index>::value>::CaseT;
+
+  template <size_t Index> static inline T make(CaseT<Index> value) {
+    return UnionCaseMeta<T, CaseId<Index>::value>::make(std::move(value));
+  }
+};
 
 template <typename T>
 using decay_t = std::remove_cv_t<std::remove_reference_t<T>>;
@@ -202,81 +309,87 @@ inline void write_union_value_data(const T &value, 
WriteContext &ctx,
                                    uint32_t type_id) {
   using Inner = union_unwrap_optional_inner_t<T>;
   const Inner &inner = unwrap_union_value(value);
-  switch (static_cast<TypeId>(type_id)) {
-  case TypeId::VAR_UINT32:
-    ctx.write_varuint32(static_cast<uint32_t>(inner));
-    return;
-  case TypeId::UINT32:
-    ctx.buffer().WriteInt32(static_cast<int32_t>(inner));
-    return;
-  case TypeId::VAR_UINT64:
-    ctx.write_varuint64(static_cast<uint64_t>(inner));
-    return;
-  case TypeId::UINT64:
-    ctx.buffer().WriteInt64(static_cast<int64_t>(inner));
-    return;
-  case TypeId::TAGGED_UINT64:
-    ctx.write_tagged_uint64(static_cast<uint64_t>(inner));
-    return;
-  case TypeId::VARINT32:
-    ctx.write_varint32(static_cast<int32_t>(inner));
-    return;
-  case TypeId::INT32:
-    ctx.buffer().WriteInt32(static_cast<int32_t>(inner));
-    return;
-  case TypeId::VARINT64:
-    ctx.write_varint64(static_cast<int64_t>(inner));
-    return;
-  case TypeId::INT64:
-    ctx.buffer().WriteInt64(static_cast<int64_t>(inner));
-    return;
-  case TypeId::TAGGED_INT64:
-    ctx.write_tagged_int64(static_cast<int64_t>(inner));
-    return;
-  default:
-    Serializer<Inner>::write_data(inner, ctx);
-    return;
+  if constexpr (std::is_integral_v<Inner> || std::is_enum_v<Inner>) {
+    switch (static_cast<TypeId>(type_id)) {
+    case TypeId::VAR_UINT32:
+      ctx.write_varuint32(static_cast<uint32_t>(inner));
+      return;
+    case TypeId::UINT32:
+      ctx.buffer().WriteInt32(static_cast<int32_t>(inner));
+      return;
+    case TypeId::VAR_UINT64:
+      ctx.write_varuint64(static_cast<uint64_t>(inner));
+      return;
+    case TypeId::UINT64:
+      ctx.buffer().WriteInt64(static_cast<int64_t>(inner));
+      return;
+    case TypeId::TAGGED_UINT64:
+      ctx.write_tagged_uint64(static_cast<uint64_t>(inner));
+      return;
+    case TypeId::VARINT32:
+      ctx.write_varint32(static_cast<int32_t>(inner));
+      return;
+    case TypeId::INT32:
+      ctx.buffer().WriteInt32(static_cast<int32_t>(inner));
+      return;
+    case TypeId::VARINT64:
+      ctx.write_varint64(static_cast<int64_t>(inner));
+      return;
+    case TypeId::INT64:
+      ctx.buffer().WriteInt64(static_cast<int64_t>(inner));
+      return;
+    case TypeId::TAGGED_INT64:
+      ctx.write_tagged_int64(static_cast<int64_t>(inner));
+      return;
+    default:
+      break;
+    }
   }
+  Serializer<Inner>::write_data(inner, ctx);
 }
 
 template <typename T>
 inline T read_union_value_data(ReadContext &ctx, uint32_t type_id) {
   using Inner = union_unwrap_optional_inner_t<T>;
   Inner value{};
-  switch (static_cast<TypeId>(type_id)) {
-  case TypeId::VAR_UINT32:
-    value = static_cast<Inner>(ctx.read_varuint32(ctx.error()));
-    break;
-  case TypeId::UINT32:
-    value = static_cast<Inner>(ctx.read_uint32(ctx.error()));
-    break;
-  case TypeId::VAR_UINT64:
-    value = static_cast<Inner>(ctx.read_varuint64(ctx.error()));
-    break;
-  case TypeId::UINT64:
-    value = static_cast<Inner>(ctx.read_uint64(ctx.error()));
-    break;
-  case TypeId::TAGGED_UINT64:
-    value = static_cast<Inner>(ctx.read_tagged_uint64(ctx.error()));
-    break;
-  case TypeId::VARINT32:
-    value = static_cast<Inner>(ctx.read_varint32(ctx.error()));
-    break;
-  case TypeId::INT32:
-    value = static_cast<Inner>(ctx.read_int32(ctx.error()));
-    break;
-  case TypeId::VARINT64:
-    value = static_cast<Inner>(ctx.read_varint64(ctx.error()));
-    break;
-  case TypeId::INT64:
-    value = static_cast<Inner>(ctx.read_int64(ctx.error()));
-    break;
-  case TypeId::TAGGED_INT64:
-    value = static_cast<Inner>(ctx.read_tagged_int64(ctx.error()));
-    break;
-  default:
+  if constexpr (std::is_integral_v<Inner> || std::is_enum_v<Inner>) {
+    switch (static_cast<TypeId>(type_id)) {
+    case TypeId::VAR_UINT32:
+      value = static_cast<Inner>(ctx.read_varuint32(ctx.error()));
+      break;
+    case TypeId::UINT32:
+      value = static_cast<Inner>(ctx.read_uint32(ctx.error()));
+      break;
+    case TypeId::VAR_UINT64:
+      value = static_cast<Inner>(ctx.read_varuint64(ctx.error()));
+      break;
+    case TypeId::UINT64:
+      value = static_cast<Inner>(ctx.read_uint64(ctx.error()));
+      break;
+    case TypeId::TAGGED_UINT64:
+      value = static_cast<Inner>(ctx.read_tagged_uint64(ctx.error()));
+      break;
+    case TypeId::VARINT32:
+      value = static_cast<Inner>(ctx.read_varint32(ctx.error()));
+      break;
+    case TypeId::INT32:
+      value = static_cast<Inner>(ctx.read_int32(ctx.error()));
+      break;
+    case TypeId::VARINT64:
+      value = static_cast<Inner>(ctx.read_varint64(ctx.error()));
+      break;
+    case TypeId::INT64:
+      value = static_cast<Inner>(ctx.read_int64(ctx.error()));
+      break;
+    case TypeId::TAGGED_INT64:
+      value = static_cast<Inner>(ctx.read_tagged_int64(ctx.error()));
+      break;
+    default:
+      value = Serializer<Inner>::read_data(ctx);
+      break;
+    }
+  } else {
     value = Serializer<Inner>::read_data(ctx);
-    break;
   }
   if (FORY_PREDICT_FALSE(ctx.has_error())) {
     return default_union_case_value<T>();
@@ -286,10 +399,10 @@ inline T read_union_value_data(ReadContext &ctx, uint32_t 
type_id) {
 
 template <typename T, typename F, size_t Index = 0>
 inline bool dispatch_union_case(uint32_t case_id, F &&fn) {
-  if constexpr (Index < UnionCaseIds<T>::case_count) {
-    constexpr uint32_t id = UnionCaseIds<T>::case_ids[Index];
+  if constexpr (Index < UnionInfo<T>::case_count) {
+    constexpr uint32_t id = UnionInfo<T>::template CaseId<Index>::value;
     if (case_id == id) {
-      fn(std::integral_constant<uint32_t, id>{});
+      fn(std::integral_constant<size_t, Index>{});
       return true;
     }
     return dispatch_union_case<T, F, Index + 1>(case_id, std::forward<F>(fn));
@@ -355,12 +468,13 @@ struct Serializer<T, 
std::enable_if_t<detail::is_union_type_v<T>>> {
     ctx.write_varuint32(case_id);
 
     bool matched = detail::dispatch_union_case<T>(case_id, [&](auto tag) {
-      using CaseMeta = UnionCaseMeta<T, tag.value>;
-      using CaseT = typename CaseMeta::CaseT;
-      constexpr ::fory::FieldMeta meta = CaseMeta::meta;
+      constexpr size_t index = decltype(tag)::value;
+      using CaseT = typename detail::UnionInfo<T>::template CaseT<index>;
+      constexpr ::fory::FieldMeta meta =
+          detail::UnionInfo<T>::template Meta<index>::value;
       constexpr uint32_t field_type_id =
           detail::resolve_union_type_id<CaseT>(meta);
-      constexpr bool manual = detail::needs_manual_encoding<CaseT>(meta);
+      const bool manual = detail::needs_manual_encoding<CaseT>(meta);
       constexpr bool nullable =
           meta.nullable_ || is_nullable_v<detail::decay_t<CaseT>>;
       const RefMode value_ref_mode =
@@ -369,7 +483,7 @@ struct Serializer<T, 
std::enable_if_t<detail::is_union_type_v<T>>> {
       obj.visit([&](const auto &value) {
         using ValueType = std::decay_t<decltype(value)>;
         if constexpr (std::is_same_v<ValueType, CaseT>) {
-          if constexpr (manual) {
+          if (manual) {
             if (!detail::write_union_ref_flag(value, ctx, value_ref_mode,
                                               nullable)) {
               return;
@@ -436,18 +550,19 @@ struct Serializer<T, 
std::enable_if_t<detail::is_union_type_v<T>>> {
 
     T result{};
     bool matched = detail::dispatch_union_case<T>(case_id, [&](auto tag) {
-      using CaseMeta = UnionCaseMeta<T, tag.value>;
-      using CaseT = typename CaseMeta::CaseT;
-      constexpr ::fory::FieldMeta meta = CaseMeta::meta;
+      constexpr size_t index = decltype(tag)::value;
+      using CaseT = typename detail::UnionInfo<T>::template CaseT<index>;
+      constexpr ::fory::FieldMeta meta =
+          detail::UnionInfo<T>::template Meta<index>::value;
       constexpr uint32_t field_type_id =
           detail::resolve_union_type_id<CaseT>(meta);
-      constexpr bool manual = detail::needs_manual_encoding<CaseT>(meta);
+      const bool manual = detail::needs_manual_encoding<CaseT>(meta);
       constexpr bool nullable =
           meta.nullable_ || is_nullable_v<detail::decay_t<CaseT>>;
       const RefMode value_ref_mode =
           meta.ref_ ? RefMode::Tracking : RefMode::NullOnly;
 
-      if constexpr (manual) {
+      if (manual) {
         bool is_null = false;
         if (!detail::read_union_ref_flag(ctx, value_ref_mode, nullable,
                                          is_null)) {
@@ -455,7 +570,8 @@ struct Serializer<T, 
std::enable_if_t<detail::is_union_type_v<T>>> {
           return;
         }
         if (is_null) {
-          result = CaseMeta::make(detail::default_union_case_value<CaseT>());
+          result = detail::UnionInfo<T>::template make<index>(
+              detail::default_union_case_value<CaseT>());
           return;
         }
         uint32_t actual_type_id = ctx.read_varuint32(ctx.error());
@@ -473,7 +589,7 @@ struct Serializer<T, 
std::enable_if_t<detail::is_union_type_v<T>>> {
           result = default_value();
           return;
         }
-        result = CaseMeta::make(std::move(value));
+        result = detail::UnionInfo<T>::template make<index>(std::move(value));
         return;
       }
 
@@ -482,7 +598,7 @@ struct Serializer<T, 
std::enable_if_t<detail::is_union_type_v<T>>> {
         result = default_value();
         return;
       }
-      result = CaseMeta::make(std::move(value));
+      result = detail::UnionInfo<T>::template make<index>(std::move(value));
     });
 
     if (FORY_PREDICT_FALSE(!matched)) {
@@ -531,10 +647,8 @@ struct Serializer<T, 
std::enable_if_t<detail::is_union_type_v<T>>> {
 
 private:
   static inline T default_value() {
-    constexpr uint32_t default_case_id = UnionCaseIds<T>::case_ids[0];
-    using DefaultMeta = UnionCaseMeta<T, default_case_id>;
-    using DefaultType = typename DefaultMeta::CaseT;
-    return DefaultMeta::make(DefaultType{});
+    using DefaultType = typename detail::UnionInfo<T>::template CaseT<0>;
+    return detail::UnionInfo<T>::template make<0>(DefaultType{});
   }
 };
 
@@ -542,31 +656,6 @@ private:
 // Union registration macros for generated code
 // 
============================================================================//
 
-#define FORY_UNION_IDS(Type, ...)                                              
\
-  namespace fory {                                                             
\
-  namespace serialization {                                                    
\
-  template <> struct UnionCaseIds<Type> {                                      
\
-    static inline constexpr uint32_t case_ids[] = {__VA_ARGS__};               
\
-    static constexpr size_t case_count =                                       
\
-        sizeof(case_ids) / sizeof(case_ids[0]);                                
\
-  };                                                                           
\
-  }                                                                            
\
-  }
-
-#define FORY_UNION_CASE(Type, CaseId, CaseType, Factory, MetaExpr)             
\
-  namespace fory {                                                             
\
-  namespace serialization {                                                    
\
-  template <> struct UnionCaseMeta<Type, CaseId> {                             
\
-    using UnionType = Type;                                                    
\
-    using CaseT = CaseType;                                                    
\
-    static constexpr ::fory::FieldMeta meta = MetaExpr;                        
\
-    static inline UnionType make(CaseT value) {                                
\
-      return Factory(std::move(value));                                        
\
-    }                                                                          
\
-  };                                                                           
\
-  }                                                                            
\
-  }
-
 #define FORY_UNION_CASE_TYPE(tuple) FORY_UNION_CASE_TYPE_IMPL tuple
 #define FORY_UNION_CASE_TYPE_IMPL(type, name, meta) type
 
@@ -656,39 +745,131 @@ private:
   M(A, _8)                                                                     
\
   M(A, _9) M(A, _10) M(A, _11) M(A, _12) M(A, _13) M(A, _14) M(A, _15) M(A, 
_16)
 
+#define FORY_UNION_PP_FOREACH_2_COMMA(M, A, ...)                               
\
+  FORY_PP_INVOKE(FORY_PP_CONCAT(FORY_UNION_PP_FOREACH_2_COMMA_IMPL_,           
\
+                                FORY_PP_NARG(__VA_ARGS__)),                    
\
+                 M, A, __VA_ARGS__)
+
+#define FORY_UNION_PP_FOREACH_2_COMMA_IMPL_1(M, A, _1) M(A, _1)
+#define FORY_UNION_PP_FOREACH_2_COMMA_IMPL_2(M, A, _1, _2) M(A, _1), M(A, _2)
+#define FORY_UNION_PP_FOREACH_2_COMMA_IMPL_3(M, A, _1, _2, _3)                 
\
+  M(A, _1), M(A, _2), M(A, _3)
+#define FORY_UNION_PP_FOREACH_2_COMMA_IMPL_4(M, A, _1, _2, _3, _4)             
\
+  M(A, _1), M(A, _2), M(A, _3), M(A, _4)
+#define FORY_UNION_PP_FOREACH_2_COMMA_IMPL_5(M, A, _1, _2, _3, _4, _5)         
\
+  M(A, _1), M(A, _2), M(A, _3), M(A, _4), M(A, _5)
+#define FORY_UNION_PP_FOREACH_2_COMMA_IMPL_6(M, A, _1, _2, _3, _4, _5, _6)     
\
+  M(A, _1), M(A, _2), M(A, _3), M(A, _4), M(A, _5), M(A, _6)
+#define FORY_UNION_PP_FOREACH_2_COMMA_IMPL_7(M, A, _1, _2, _3, _4, _5, _6, _7) 
\
+  M(A, _1), M(A, _2), M(A, _3), M(A, _4), M(A, _5), M(A, _6), M(A, _7)
+#define FORY_UNION_PP_FOREACH_2_COMMA_IMPL_8(M, A, _1, _2, _3, _4, _5, _6, _7, 
\
+                                             _8)                               
\
+  M(A, _1), M(A, _2), M(A, _3), M(A, _4), M(A, _5), M(A, _6), M(A, _7), M(A, 
_8)
+#define FORY_UNION_PP_FOREACH_2_COMMA_IMPL_9(M, A, _1, _2, _3, _4, _5, _6, _7, 
\
+                                             _8, _9)                           
\
+  M(A, _1), M(A, _2), M(A, _3), M(A, _4), M(A, _5), M(A, _6), M(A, _7),        
\
+      M(A, _8), M(A, _9)
+#define FORY_UNION_PP_FOREACH_2_COMMA_IMPL_10(M, A, _1, _2, _3, _4, _5, _6,    
\
+                                              _7, _8, _9, _10)                 
\
+  M(A, _1), M(A, _2), M(A, _3), M(A, _4), M(A, _5), M(A, _6), M(A, _7),        
\
+      M(A, _8), M(A, _9), M(A, _10)
+#define FORY_UNION_PP_FOREACH_2_COMMA_IMPL_11(M, A, _1, _2, _3, _4, _5, _6,    
\
+                                              _7, _8, _9, _10, _11)            
\
+  M(A, _1), M(A, _2), M(A, _3), M(A, _4), M(A, _5), M(A, _6), M(A, _7),        
\
+      M(A, _8), M(A, _9), M(A, _10), M(A, _11)
+#define FORY_UNION_PP_FOREACH_2_COMMA_IMPL_12(M, A, _1, _2, _3, _4, _5, _6,    
\
+                                              _7, _8, _9, _10, _11, _12)       
\
+  M(A, _1), M(A, _2), M(A, _3), M(A, _4), M(A, _5), M(A, _6), M(A, _7),        
\
+      M(A, _8), M(A, _9), M(A, _10), M(A, _11), M(A, _12)
+#define FORY_UNION_PP_FOREACH_2_COMMA_IMPL_13(M, A, _1, _2, _3, _4, _5, _6,    
\
+                                              _7, _8, _9, _10, _11, _12, _13)  
\
+  M(A, _1), M(A, _2), M(A, _3), M(A, _4), M(A, _5), M(A, _6), M(A, _7),        
\
+      M(A, _8), M(A, _9), M(A, _10), M(A, _11), M(A, _12), M(A, _13)
+#define FORY_UNION_PP_FOREACH_2_COMMA_IMPL_14(                                 
\
+    M, A, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14)         
\
+  M(A, _1), M(A, _2), M(A, _3), M(A, _4), M(A, _5), M(A, _6), M(A, _7),        
\
+      M(A, _8), M(A, _9), M(A, _10), M(A, _11), M(A, _12), M(A, _13),          
\
+      M(A, _14)
+#define FORY_UNION_PP_FOREACH_2_COMMA_IMPL_15(                                 
\
+    M, A, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15)    
\
+  M(A, _1), M(A, _2), M(A, _3), M(A, _4), M(A, _5), M(A, _6), M(A, _7),        
\
+      M(A, _8), M(A, _9), M(A, _10), M(A, _11), M(A, _12), M(A, _13),          
\
+      M(A, _14), M(A, _15)
+#define FORY_UNION_PP_FOREACH_2_COMMA_IMPL_16(M, A, _1, _2, _3, _4, _5, _6,    
\
+                                              _7, _8, _9, _10, _11, _12, _13,  
\
+                                              _14, _15, _16)                   
\
+  M(A, _1), M(A, _2), M(A, _3), M(A, _4), M(A, _5), M(A, _6), M(A, _7),        
\
+      M(A, _8), M(A, _9), M(A, _10), M(A, _11), M(A, _12), M(A, _13),          
\
+      M(A, _14), M(A, _15), M(A, _16)
+
 #define FORY_UNION_CASE_ID(Type, tuple)                                        
\
   static_cast<uint32_t>(FORY_UNION_CASE_META(tuple).id_)
 
 #define FORY_UNION_CASE_ID_ENTRY(Type, tuple) FORY_UNION_CASE_ID(Type, tuple),
 
-#define FORY_UNION_CASE_DEF(Type, tuple)                                       
\
-  template <>                                                                  
\
-  struct UnionCaseMeta<Type, static_cast<uint32_t>(                            
\
-                                 FORY_UNION_CASE_META(tuple).id_)> {           
\
-    using UnionType = Type;                                                    
\
-    using CaseT = FORY_UNION_CASE_TYPE(tuple);                                 
\
-    static constexpr ::fory::FieldMeta meta = FORY_UNION_CASE_META(tuple);     
\
-    static inline UnionType make(CaseT value) {                                
\
-      return Type::FORY_UNION_CASE_NAME(tuple)(std::move(value));              
\
-    }                                                                          
\
-  };
+#define FORY_UNION_CASE_TYPE_VALUE(Type, tuple) FORY_UNION_CASE_TYPE(tuple)
+#define FORY_UNION_CASE_META_VALUE(Type, tuple) FORY_UNION_CASE_META(tuple)
+#define FORY_UNION_CASE_ID_VALUE(Type, tuple) FORY_UNION_CASE_ID(Type, tuple)
+#define FORY_UNION_CASE_FACTORY_VALUE(Type, tuple)                             
\
+  static_cast<Type (*)(FORY_UNION_CASE_TYPE(tuple))>(                          
\
+      &Type::FORY_UNION_CASE_NAME(tuple))
+
+#define FORY_UNION_IDS_DESCRIPTOR_NAME(line)                                   
\
+  FORY_PP_CONCAT(ForyUnionCaseIdsDescriptor_, line)
+
+#define FORY_UNION_CASE_DESCRIPTOR_NAME(line)                                  
\
+  FORY_PP_CONCAT(ForyUnionCaseMetaDescriptor_, line)
+
+#define FORY_UNION_DESCRIPTOR_NAME(line)                                       
\
+  FORY_PP_CONCAT(ForyUnionInfoDescriptor_, line)
+
+#define FORY_UNION_IDS(Type, ...)                                              
\
+  struct FORY_UNION_IDS_DESCRIPTOR_NAME(__LINE__) {                            
\
+    static inline constexpr uint32_t case_ids[] = {__VA_ARGS__};               
\
+    static constexpr size_t case_count =                                       
\
+        sizeof(case_ids) / sizeof(case_ids[0]);                                
\
+  };                                                                           
\
+  constexpr auto ForyUnionCaseIds(::fory::meta::Identity<Type>) {              
\
+    return FORY_UNION_IDS_DESCRIPTOR_NAME(__LINE__){};                         
\
+  }                                                                            
\
+  static_assert(true)
+
+#define FORY_UNION_CASE(Type, CaseId, CaseType, Factory, MetaExpr)             
\
+  struct FORY_UNION_CASE_DESCRIPTOR_NAME(__LINE__) {                           
\
+    using CaseT = CaseType;                                                    
\
+    static constexpr ::fory::FieldMeta meta = MetaExpr;                        
\
+    static inline Type make(CaseT value) { return Factory(std::move(value)); } 
\
+  };                                                                           
\
+  constexpr auto ForyUnionCaseMeta(::fory::meta::Identity<Type>,               
\
+                                   std::integral_constant<uint32_t, CaseId>) { 
\
+    return FORY_UNION_CASE_DESCRIPTOR_NAME(__LINE__){};                        
\
+  }                                                                            
\
+  static_assert(true)
 
 #define FORY_UNION(Type, ...)                                                  
\
   static_assert(FORY_PP_NARG(__VA_ARGS__) <= 16,                               
\
                 "FORY_UNION supports up to 16 cases; use "                     
\
                 "FORY_UNION_IDS/FORY_UNION_CASE "                              
\
                 "for larger unions");                                          
\
-  namespace fory {                                                             
\
-  namespace serialization {                                                    
\
-  template <> struct UnionCaseIds<Type> {                                      
\
-    static inline constexpr uint32_t case_ids[] = {                            
\
-        FORY_UNION_PP_FOREACH_2(FORY_UNION_CASE_ID_ENTRY, Type, __VA_ARGS__)}; 
\
-    static constexpr size_t case_count =                                       
\
-        sizeof(case_ids) / sizeof(case_ids[0]);                                
\
+  struct FORY_UNION_DESCRIPTOR_NAME(__LINE__) {                                
\
+    using UnionType = Type;                                                    
\
+    static constexpr size_t case_count = FORY_PP_NARG(__VA_ARGS__);            
\
+    using CaseTypes = std::tuple<FORY_UNION_PP_FOREACH_2_COMMA(                
\
+        FORY_UNION_CASE_TYPE_VALUE, Type, __VA_ARGS__)>;                       
\
+    static inline constexpr std::array<uint32_t, case_count> case_ids = {      
\
+        FORY_UNION_PP_FOREACH_2_COMMA(FORY_UNION_CASE_ID_VALUE, Type,          
\
+                                      __VA_ARGS__)};                           
\
+    static inline constexpr std::array<::fory::FieldMeta, case_count>          
\
+        case_metas = {FORY_UNION_PP_FOREACH_2_COMMA(                           
\
+            FORY_UNION_CASE_META_VALUE, Type, __VA_ARGS__)};                   
\
+    static inline constexpr auto factories =                                   
\
+        std::make_tuple(FORY_UNION_PP_FOREACH_2_COMMA(                         
\
+            FORY_UNION_CASE_FACTORY_VALUE, Type, __VA_ARGS__));                
\
   };                                                                           
\
-  FORY_UNION_PP_FOREACH_2(FORY_UNION_CASE_DEF, Type, __VA_ARGS__)              
\
+  constexpr auto ForyUnionInfo(::fory::meta::Identity<Type>) {                 
\
+    return FORY_UNION_DESCRIPTOR_NAME(__LINE__){};                             
\
   }                                                                            
\
-  }
+  static_assert(true)
 
 } // namespace serialization
 } // namespace fory
diff --git a/docs/guide/cpp/index.md b/docs/guide/cpp/index.md
index fefaef00d..e0bc24a34 100644
--- a/docs/guide/cpp/index.md
+++ b/docs/guide/cpp/index.md
@@ -141,7 +141,7 @@ See the 
[examples/cpp](https://github.com/apache/fory/tree/main/examples/cpp) di
 using namespace fory::serialization;
 
 // Define a struct
-struct Person {
+class Person {
   std::string name;
   int32_t age;
   std::vector<std::string> hobbies;
@@ -149,10 +149,13 @@ struct Person {
   bool operator==(const Person &other) const {
     return name == other.name && age == other.age && hobbies == other.hobbies;
   }
+
+public:
+  // Register the struct with Fory (FORY_STRUCT must be in public scope).
+  FORY_STRUCT(Person, name, age, hobbies);
 };
 
-// Register the struct with Fory (must be in the same namespace)
-FORY_STRUCT(Person, name, age, hobbies);
+
 
 int main() {
   // Create a Fory instance
diff --git a/docs/guide/cpp/supported-types.md 
b/docs/guide/cpp/supported-types.md
index fb020e00a..889540c34 100644
--- a/docs/guide/cpp/supported-types.md
+++ b/docs/guide/cpp/supported-types.md
@@ -253,6 +253,7 @@ enum class Color { RED = 0, GREEN = 1, BLUE = 2 };
 // Unscoped enum with incontinuous values
 enum Priority : int32_t { LOW = -10, NORMAL = 0, HIGH = 10 };
 FORY_ENUM(Priority, LOW, NORMAL, HIGH);
+// FORY_ENUM must be defined at namespace scope.
 
 // Usage
 Color c = Color::GREEN;
diff --git a/docs/guide/cpp/type-registration.md 
b/docs/guide/cpp/type-registration.md
index 79b2d367f..e25618495 100644
--- a/docs/guide/cpp/type-registration.md
+++ b/docs/guide/cpp/type-registration.md
@@ -90,6 +90,7 @@ For enums with non-continuous values, use the `FORY_ENUM` 
macro to map values to
 // Non-continuous enum values - FORY_ENUM required
 enum class Priority { LOW = 10, MEDIUM = 50, HIGH = 100 };
 FORY_ENUM(Priority, LOW, MEDIUM, HIGH);
+// FORY_ENUM must be defined at namespace scope.
 
 // Global namespace enum (prefix with ::)
 enum LegacyStatus { UNKNOWN = -1, OK = 0, ERROR = 1 };


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

Reply via email to