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]