Skip to content

[C++] Create fory::field<> template for field metadata #3003

@chaokunyang

Description

@chaokunyang

Add a fory::field<> template wrapper for compile-time field metadata to enable performance and space optimization during xlang serialization.

Problem Statement

Current Limitations

The current C++ implementation applies uniform handling to all struct fields:

  1. Null Checks: Written for all non-primitive fields, even when they can never be null (1 byte per field overhead)
  2. Reference Tracking: Applied globally regardless of whether fields actually need shared reference support
  3. Field Name Encoding: Long field names consume extra space via meta string compression instead of compact tag IDs

Expected Benefits

For a 10-field structure with tag IDs and explicit nullable/ref settings:

  • Space savings: ~20 bytes per object (eliminates null flags when not needed)
  • CPU savings: 10 fewer hash operations per serialization (no meta string lookups for field names)
  • Compact encoding: Varint tag IDs (1-2 bytes) replace meta string encoded names (~5-15 bytes)

Proposed Solution

Core Principles

  1. All fields are non-nullable by default (except std::optional)
  2. All fields have no reference tracking by default
  3. Only 2 tags: fory::nullable and fory::ref
  4. Type-safe: nullable only valid for pointer types, ref only valid for shared_ptr

API Design

namespace fory {
  struct nullable {};  // Mark shared_ptr/unique_ptr as nullable
  struct ref {};       // Enable reference tracking for shared_ptr

  template <typename T, int16_t Id, typename... Options>
  class field { /* same memory layout as T */ };
}

Type Rules

Type Default Nullable Default Ref Allowed Options
Primitives, std::string false false None (no options allowed)
std::optional<T> true (inherent) false None (inherent nullable)
std::shared_ptr<T> false false fory::nullable, fory::ref
std::unique_ptr<T> false false fory::nullable only

Usage Example

struct Document {
  // Primitives - always non-nullable, NO options allowed
  fory::field<std::string, 0> title;
  fory::field<int32_t, 1> version;

  // std::optional - always nullable (inherent), NO options needed
  fory::field<std::optional<std::string>, 2> description;

  // shared_ptr - non-nullable by default
  fory::field<std::shared_ptr<User>, 3> author;                        // must exist
  fory::field<std::shared_ptr<User>, 4, fory::nullable> reviewer;      // can be null
  fory::field<std::shared_ptr<Node>, 5, fory::ref> parent;             // ref tracking
  fory::field<std::shared_ptr<Node>, 6, fory::nullable, fory::ref> p;  // nullable + ref

  // unique_ptr - non-nullable by default, no ref allowed
  fory::field<std::unique_ptr<Data>, 7> data;                          // must exist
  fory::field<std::unique_ptr<Data>, 8, fory::nullable> opt_data;      // can be null
};

FORY_STRUCT(Document, title, version, description, author, reviewer, parent, p, data, opt_data);

Compile-Time Validation

Invalid configurations are caught at compile time:

// ❌ ERROR: nullable not allowed on primitives
fory::field<int32_t, 0, fory::nullable> age;

// ❌ ERROR: nullable not allowed on string
fory::field<std::string, 1, fory::nullable> name;

// ❌ ERROR: ref not allowed on unique_ptr
fory::field<std::unique_ptr<T>, 2, fory::ref> ptr;

// ❌ ERROR: options not allowed on optional (inherently nullable)
fory::field<std::optional<T>, 3, fory::nullable> opt;

// ✅ CORRECT: use optional for nullable primitives
fory::field<std::optional<int32_t>, 0> age;
fory::field<std::optional<std::string>, 1> name;

Runtime Behavior

Serialization (Strict)

Field Config Value Behavior
Non-nullable shared_ptr<T> nullptr ❌ Error: "Cannot serialize null for non-nullable field"
Non-nullable shared_ptr<T> valid ptr ✅ Serialize directly (no null flag written)
fory::nullable shared_ptr<T> nullptr ✅ Write null flag
fory::nullable shared_ptr<T> valid ptr ✅ Write non-null flag + data

Deserialization (Lenient)

Field Config Remote Sends Result
Non-nullable shared_ptr<T> null/missing std::make_shared<T>() (default T)
Non-nullable unique_ptr<T> null/missing std::make_unique<T>() (default T)
fory::nullable shared_ptr<T> null nullptr
fory::nullable unique_ptr<T> null nullptr

Rationale: Be strict in what you send, lenient in what you accept.

Additional Context

This is the C++ equivalent of Java's @ForyField annotation. See Java issue #3000 for the original design discussion.

Protocol spec: https://fory.apache.org/docs/specification/fory_xlang_serialization_spec

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions