// Glaze Library // For the license information refer to glaze.hpp #pragma once #include "glaze/cbor/header.hpp" #include "glaze/core/buffer_traits.hpp" #include "glaze/core/chrono.hpp" #include "glaze/core/opts.hpp" #include "glaze/core/reflect.hpp" #include "glaze/core/to.hpp" #include "glaze/core/write.hpp" #include "glaze/util/dump.hpp" #include "glaze/util/for_each.hpp" #include "glaze/util/variant.hpp" namespace glz { template <> struct serialize { template GLZ_ALWAYS_INLINE static void op(T&& value, Ctx&& ctx, B&& b, IX&& ix) { to>::template op(std::forward(value), std::forward(ctx), std::forward(b), std::forward(ix)); } }; namespace cbor_detail { // Dump a single byte to the buffer (context-aware version with overflow checking) template GLZ_ALWAYS_INLINE bool dump_byte(is_context auto& ctx, uint8_t byte, B& b, IX& ix) { if (!ensure_space(ctx, b, ix + 1 + write_padding_bytes)) [[unlikely]] { return false; } b[ix] = static_cast::value_type>(byte); ++ix; return true; } // Legacy dump_byte for internal use (no context) template GLZ_ALWAYS_INLINE void dump_byte(uint8_t byte, B& b, IX& ix) { if (ix >= b.size()) [[unlikely]] { b.resize(b.size() == 0 ? 128 : b.size() * 2); } b[ix] = static_cast::value_type>(byte); ++ix; } // Dump multiple bytes to the buffer (big-endian for CBOR, context-aware) template GLZ_ALWAYS_INLINE bool dump_be(is_context auto& ctx, T value, B& b, IX& ix) { constexpr auto n = sizeof(T); if (!ensure_space(ctx, b, ix + n + write_padding_bytes)) [[unlikely]] { return false; } if constexpr (std::endian::native == std::endian::little && n > 1) { value = std::byteswap(value); } std::memcpy(&b[ix], &value, n); ix += n; return true; } // Legacy dump_be for internal use (no context) template GLZ_ALWAYS_INLINE void dump_be(T value, B& b, IX& ix) { constexpr auto n = sizeof(T); if (const auto k = ix + n; k > b.size()) [[unlikely]] { b.resize(2 * k); } if constexpr (std::endian::native == std::endian::little && n > 1) { value = std::byteswap(value); } std::memcpy(&b[ix], &value, n); ix += n; } // Encode CBOR argument with minimal bytes (context-aware version) template GLZ_ALWAYS_INLINE bool encode_arg(is_context auto& ctx, uint8_t major_type, uint64_t value, B& b, IX& ix) { using namespace cbor; if (value < 24) { return dump_byte(ctx, initial_byte(major_type, static_cast(value)), b, ix); } else if (value <= 0xFF) { if (!dump_byte(ctx, initial_byte(major_type, info::uint8_follows), b, ix)) return false; return dump_byte(ctx, static_cast(value), b, ix); } else if (value <= 0xFFFF) { if (!dump_byte(ctx, initial_byte(major_type, info::uint16_follows), b, ix)) return false; return dump_be(ctx, static_cast(value), b, ix); } else if (value <= 0xFFFFFFFF) { if (!dump_byte(ctx, initial_byte(major_type, info::uint32_follows), b, ix)) return false; return dump_be(ctx, static_cast(value), b, ix); } else { if (!dump_byte(ctx, initial_byte(major_type, info::uint64_follows), b, ix)) return false; return dump_be(ctx, value, b, ix); } } // Legacy encode_arg for internal use (no context) template GLZ_ALWAYS_INLINE void encode_arg(uint8_t major_type, uint64_t value, B& b, IX& ix) { using namespace cbor; if (value < 24) { dump_byte(initial_byte(major_type, static_cast(value)), b, ix); } else if (value <= 0xFF) { dump_byte(initial_byte(major_type, info::uint8_follows), b, ix); dump_byte(static_cast(value), b, ix); } else if (value <= 0xFFFF) { dump_byte(initial_byte(major_type, info::uint16_follows), b, ix); dump_be(static_cast(value), b, ix); } else if (value <= 0xFFFFFFFF) { dump_byte(initial_byte(major_type, info::uint32_follows), b, ix); dump_be(static_cast(value), b, ix); } else { dump_byte(initial_byte(major_type, info::uint64_follows), b, ix); dump_be(value, b, ix); } } // Compile-time version for known sizes (context-aware) template GLZ_ALWAYS_INLINE bool encode_arg_cx(is_context auto& ctx, uint8_t major_type, B& b, IX& ix) { using namespace cbor; if constexpr (value < 24) { return dump_byte(ctx, initial_byte(major_type, static_cast(value)), b, ix); } else if constexpr (value <= 0xFF) { if (!dump_byte(ctx, initial_byte(major_type, info::uint8_follows), b, ix)) return false; return dump_byte(ctx, static_cast(value), b, ix); } else if constexpr (value <= 0xFFFF) { if (!dump_byte(ctx, initial_byte(major_type, info::uint16_follows), b, ix)) return false; return dump_be(ctx, static_cast(value), b, ix); } else if constexpr (value <= 0xFFFFFFFF) { if (!dump_byte(ctx, initial_byte(major_type, info::uint32_follows), b, ix)) return false; return dump_be(ctx, static_cast(value), b, ix); } else { if (!dump_byte(ctx, initial_byte(major_type, info::uint64_follows), b, ix)) return false; return dump_be(ctx, value, b, ix); } } // Legacy compile-time version for internal use (no context) template GLZ_ALWAYS_INLINE void encode_arg_cx(uint8_t major_type, B& b, IX& ix) { using namespace cbor; if constexpr (value < 24) { dump_byte(initial_byte(major_type, static_cast(value)), b, ix); } else if constexpr (value <= 0xFF) { dump_byte(initial_byte(major_type, info::uint8_follows), b, ix); dump_byte(static_cast(value), b, ix); } else if constexpr (value <= 0xFFFF) { dump_byte(initial_byte(major_type, info::uint16_follows), b, ix); dump_be(static_cast(value), b, ix); } else if constexpr (value <= 0xFFFFFFFF) { dump_byte(initial_byte(major_type, info::uint32_follows), b, ix); dump_be(static_cast(value), b, ix); } else { dump_byte(initial_byte(major_type, info::uint64_follows), b, ix); dump_be(value, b, ix); } } } // Null template struct to { template GLZ_ALWAYS_INLINE static void op(auto&&, is_context auto&& ctx, auto&& b, auto& ix) { cbor_detail::dump_byte(ctx, cbor::initial_byte(cbor::major::simple, cbor::simple::null_value), b, ix); } }; // Bitset - stored as byte string template struct to { template static void op(auto&& value, is_context auto&& ctx, auto&& b, auto& ix) { const auto num_bytes = (value.size() + 7) / 8; // Write byte string header if (!cbor_detail::encode_arg(ctx, cbor::major::bstr, num_bytes, b, ix)) [[unlikely]] { return; } // Pack bits into bytes (LSB first within each byte) for (size_t byte_i = 0, bit_idx = 0; byte_i < num_bytes; ++byte_i) { uint8_t byte_val = 0; for (size_t bit_i = 0; bit_i < 8 && bit_idx < value.size(); ++bit_i, ++bit_idx) { byte_val |= uint8_t(value[bit_idx]) << uint8_t(bit_i); } if (!cbor_detail::dump_byte(ctx, byte_val, b, ix)) [[unlikely]] { return; } } } }; // Complex numbers - tag 43000 with 2-element array [real, imag] template requires complex_t struct to final { template GLZ_ALWAYS_INLINE static void op(auto&& value, is_context auto&& ctx, auto&& b, auto& ix) { // Write tag 43000 (complex number) if (!cbor_detail::encode_arg(ctx, cbor::major::tag, cbor::semantic_tag::complex_number, b, ix)) [[unlikely]] { return; } // Write array header for 2 elements if (!cbor_detail::dump_byte(ctx, cbor::initial_byte(cbor::major::array, 2), b, ix)) [[unlikely]] { return; } // Write real part using V = typename std::remove_cvref_t::value_type; to::template op(value.real(), ctx, b, ix); if (bool(ctx.error)) [[unlikely]] return; // Write imaginary part to::template op(value.imag(), ctx, b, ix); } }; // Boolean template struct to final { template GLZ_ALWAYS_INLINE static void op(const bool value, is_context auto&& ctx, auto&& b, auto& ix) { using namespace cbor; const uint8_t byte = value ? initial_byte(major::simple, simple::true_value) : initial_byte(major::simple, simple::false_value); cbor_detail::dump_byte(ctx, byte, b, ix); } }; // Unsigned integers template requires(std::unsigned_integral && !std::same_as) struct to final { template GLZ_ALWAYS_INLINE static void op(auto&& value, is_context auto&& ctx, auto&& b, auto& ix) { cbor_detail::encode_arg(ctx, cbor::major::uint, static_cast(value), b, ix); } }; // Signed integers template requires(std::signed_integral && !std::same_as) struct to final { template GLZ_ALWAYS_INLINE static void op(auto&& value, is_context auto&& ctx, auto&& b, auto& ix) { if (value >= 0) { cbor_detail::encode_arg(ctx, cbor::major::uint, static_cast(value), b, ix); } else { // CBOR negative: encode n where value = -1 - n, so n = ~value // Using two's complement identity: ~value = -value - 1 = -1 - value // This safely handles INT64_MIN without overflow const uint64_t n = static_cast(~value); cbor_detail::encode_arg(ctx, cbor::major::nint, n, b, ix); } } }; // Floating-point with preferred serialization (smallest representation) template struct to final { template GLZ_ALWAYS_INLINE static void op(auto&& value, is_context auto&& ctx, auto&& b, auto& ix) { using namespace cbor; const double d = static_cast(value); // Try half precision first (preferred serialization) if (can_encode_half(d)) { if (!cbor_detail::dump_byte(ctx, initial_byte(major::simple, simple::float16), b, ix)) [[unlikely]] { return; } uint16_t half = encode_half(d); cbor_detail::dump_be(ctx, half, b, ix); return; } // Try single precision if (can_encode_float(d)) { if (!cbor_detail::dump_byte(ctx, initial_byte(major::simple, simple::float32), b, ix)) [[unlikely]] { return; } const float f = static_cast(d); uint32_t bits; std::memcpy(&bits, &f, sizeof(float)); cbor_detail::dump_be(ctx, bits, b, ix); return; } // Fall back to double precision if (!cbor_detail::dump_byte(ctx, initial_byte(major::simple, simple::float64), b, ix)) [[unlikely]] { return; } uint64_t bits; std::memcpy(&bits, &d, sizeof(double)); cbor_detail::dump_be(ctx, bits, b, ix); } }; // Text strings (UTF-8) template struct to final { template GLZ_ALWAYS_INLINE static void op(auto&& value, is_context auto&& ctx, auto&& b, auto& ix) { const sv str = [&]() -> const sv { if constexpr (!char_array_t && std::is_pointer_v>) { return value ? value : ""; } else { return sv{value}; } }(); if (!cbor_detail::encode_arg(ctx, cbor::major::tstr, str.size(), b, ix)) [[unlikely]] { return; } const auto n = str.size(); if (!ensure_space(ctx, b, ix + n + write_padding_bytes)) [[unlikely]] { return; } if (n) { std::memcpy(&b[ix], str.data(), n); ix += n; } } }; // Byte strings (std::vector, std::span, etc.) template requires(std::same_as && !str_t) struct to final { template static void op(const auto& value, is_context auto&& ctx, auto&& b, auto& ix) { if (!cbor_detail::encode_arg(ctx, cbor::major::bstr, value.size(), b, ix)) [[unlikely]] { return; } const auto n = value.size(); if (!ensure_space(ctx, b, ix + n + write_padding_bytes)) [[unlikely]] { return; } if (n) { std::memcpy(&b[ix], value.data(), n); ix += n; } } }; // std::vector as byte string template <> struct to> final { template static void op(const auto& value, is_context auto&& ctx, auto&& b, auto& ix) { if (!cbor_detail::encode_arg(ctx, cbor::major::bstr, value.size(), b, ix)) [[unlikely]] { return; } const auto n = value.size(); if (!ensure_space(ctx, b, ix + n + write_padding_bytes)) [[unlikely]] { return; } if (n) { std::memcpy(&b[ix], value.data(), n); ix += n; } } }; // std::array as byte string template struct to> final { template static void op(const auto& value, is_context auto&& ctx, auto&& b, auto& ix) { if (!cbor_detail::encode_arg_cx(ctx, cbor::major::bstr, b, ix)) [[unlikely]] { return; } if (!ensure_space(ctx, b, ix + N + write_padding_bytes)) [[unlikely]] { return; } if constexpr (N > 0) { std::memcpy(&b[ix], value.data(), N); ix += N; } } }; // std::array as byte string template struct to> final { template static void op(const auto& value, is_context auto&& ctx, auto&& b, auto& ix) { if (!cbor_detail::encode_arg_cx(ctx, cbor::major::bstr, b, ix)) [[unlikely]] { return; } if (!ensure_space(ctx, b, ix + N + write_padding_bytes)) [[unlikely]] { return; } if constexpr (N > 0) { std::memcpy(&b[ix], value.data(), N); ix += N; } } }; // Arrays (std::vector, std::array, std::deque, etc.) // Note: eigen_t types have their own specialization in glaze/ext/eigen.hpp template requires(!eigen_t) struct to final { template static void op(auto&& value, is_context auto&& ctx, auto&& b, auto& ix) { using V = range_value_t>; // Use RFC 8746 typed arrays for numeric types (bulk memcpy) if constexpr (num_t && !std::same_as && contiguous) { // Write the tag for this type using native endianness constexpr uint64_t tag = cbor::typed_array::native_tag(); if (!cbor_detail::encode_arg(ctx, cbor::major::tag, tag, b, ix)) [[unlikely]] { return; } // Write byte string header const size_t byte_len = value.size() * sizeof(V); if (!cbor_detail::encode_arg(ctx, cbor::major::bstr, byte_len, b, ix)) [[unlikely]] { return; } // Ensure buffer space if (!ensure_space(ctx, b, ix + byte_len + write_padding_bytes)) [[unlikely]] { return; } // Bulk write: native endianness matches tag if (byte_len > 0) { std::memcpy(&b[ix], value.data(), byte_len); ix += byte_len; } } else if constexpr (complex_t && contiguous) { // Complex array: tag 43001 with interleaved typed array [r0, i0, r1, i1, ...] // std::complex stores data as [real, imag] pairs contiguously using Scalar = typename V::value_type; // Write tag 43001 (complex array) if (!cbor_detail::encode_arg(ctx, cbor::major::tag, cbor::semantic_tag::complex_array, b, ix)) [[unlikely]] { return; } // Write typed array tag for the underlying scalar type constexpr uint64_t scalar_tag = cbor::typed_array::native_tag(); if (!cbor_detail::encode_arg(ctx, cbor::major::tag, scalar_tag, b, ix)) [[unlikely]] { return; } // Write byte string header (2 scalars per complex: real and imag) const size_t byte_len = value.size() * sizeof(V); // sizeof(complex) = 2 * sizeof(T) if (!cbor_detail::encode_arg(ctx, cbor::major::bstr, byte_len, b, ix)) [[unlikely]] { return; } // Ensure buffer space if (!ensure_space(ctx, b, ix + byte_len + write_padding_bytes)) [[unlikely]] { return; } // Bulk write: std::complex stores [real, imag] contiguously if (byte_len > 0) { std::memcpy(&b[ix], value.data(), byte_len); ix += byte_len; } } else { // Generic CBOR array for non-numeric or non-contiguous types if (!cbor_detail::encode_arg(ctx, cbor::major::array, value.size(), b, ix)) [[unlikely]] { return; } for (auto&& item : value) { serialize::op(item, ctx, b, ix); if (bool(ctx.error)) [[unlikely]] return; if constexpr (is_output_streaming) { flush_buffer(b, ix); } } } } }; // Maps (std::map, std::unordered_map, etc.) template struct to final { template static void op(auto&& value, is_context auto&& ctx, auto&& b, auto& ix) { using map_t = std::remove_cvref_t; using val_t = std::remove_cvref_t>; constexpr bool may_skip = null_t && Opts.skip_null_members; size_t count = value.size(); if constexpr (may_skip) { count = 0; for (auto&& [k, v] : value) { (void)k; if (!skip_member(v)) ++count; } } if (!cbor_detail::encode_arg(ctx, cbor::major::map, count, b, ix)) [[unlikely]] { return; } for (auto&& [k, v] : value) { if constexpr (may_skip) { if (skip_member(v)) continue; } serialize::op(k, ctx, b, ix); if (bool(ctx.error)) [[unlikely]] return; serialize::op(v, ctx, b, ix); if (bool(ctx.error)) [[unlikely]] return; if constexpr (is_output_streaming) { flush_buffer(b, ix); } } } }; // Pairs template struct to final { template GLZ_ALWAYS_INLINE static void op(auto&& value, is_context auto&& ctx, auto&& b, auto& ix) { if (!cbor_detail::encode_arg_cx<1>(ctx, cbor::major::map, b, ix)) [[unlikely]] { return; } const auto& [k, v] = value; serialize::op(k, ctx, b, ix); if (bool(ctx.error)) [[unlikely]] return; serialize::op(v, ctx, b, ix); } }; // Glaze objects (structs with reflection) template requires((glaze_object_t || reflectable) && !custom_write) struct to final { static constexpr auto N = reflect::size; template static consteval bool should_skip_field() { using V = field_t; if constexpr (always_skipped) { return true; } else if constexpr (is_any_function_ptr) { return !check_write_function_pointers(Opts); } else { return false; } } template static consteval size_t count_to_write() { return [](std::index_sequence) { return (size_t{} + ... + (should_skip_field() ? size_t{} : size_t{1})); }(std::make_index_sequence{}); } template static void op(auto&& value, is_context auto&& ctx, auto&& b, auto& ix) { [[maybe_unused]] decltype(auto) t = [&]() -> decltype(auto) { if constexpr (reflectable) { return to_tie(value); } else { return nullptr; } }(); if constexpr (maybe_skipped) { // Dynamic path: count members at runtime to handle skip_null_members size_t member_count = 0; // First pass: count members that will be written for_each([&]() { if constexpr (should_skip_field()) { return; } else { using val_t = field_t; if constexpr (null_t && Opts.skip_null_members) { if constexpr (always_null_t) { return; } else { const auto is_null = [&]() { decltype(auto) element = [&]() -> decltype(auto) { if constexpr (reflectable) { return get(t); } else { return get(reflect::values); } }; if constexpr (nullable_wrapper) { return !bool(element()(value).val); } else if constexpr (nullable_value_t) { return !get_member(value, element()).has_value(); } else { return !bool(get_member(value, element())); } }(); if (!is_null) { ++member_count; } } } else if constexpr (is_specialization_v && Opts.skip_null_members && custom_getter_returns_nullable()) { if (!is_custom_field_null(value, t, ctx)) { ++member_count; } } else if constexpr (Opts.skip_null_members && glaze_value_is_nullable()) { if (!is_glaze_value_field_null(value, t)) { ++member_count; } } else if constexpr (check_skip_default_members(Opts) && has_skippable_default) { decltype(auto) member = [&]() -> decltype(auto) { if constexpr (reflectable) { return get_member(value, get(t)); } else { return get_member(value, get(reflect::values)); } }(); if (!is_default_value(member)) { ++member_count; } } else { ++member_count; } } }); // Write map header with dynamic count if (!cbor_detail::encode_arg(ctx, cbor::major::map, member_count, b, ix)) [[unlikely]] { return; } // Second pass: write members for_each([&]() { if (bool(ctx.error)) [[unlikely]] return; if constexpr (should_skip_field()) { return; } else { using val_t = field_t; if constexpr (null_t && Opts.skip_null_members) { if constexpr (always_null_t) { return; } else { const auto is_null = [&]() { decltype(auto) element = [&]() -> decltype(auto) { if constexpr (reflectable) { return get(t); } else { return get(reflect::values); } }; if constexpr (nullable_wrapper) { return !bool(element()(value).val); } else if constexpr (nullable_value_t) { return !get_member(value, element()).has_value(); } else { return !bool(get_member(value, element())); } }(); if (is_null) { return; } } } else if constexpr (is_specialization_v && Opts.skip_null_members && custom_getter_returns_nullable()) { if (is_custom_field_null(value, t, ctx)) { return; } } else if constexpr (Opts.skip_null_members && glaze_value_is_nullable()) { if (is_glaze_value_field_null(value, t)) { return; } } if constexpr (check_skip_default_members(Opts) && has_skippable_default) { decltype(auto) member_val = [&]() -> decltype(auto) { if constexpr (reflectable) { return get_member(value, get(t)); } else { return get_member(value, get(reflect::values)); } }(); if (is_default_value(member_val)) { return; } } static constexpr sv key = reflect::keys[I]; // Write key as text string if (!cbor_detail::encode_arg_cx(ctx, cbor::major::tstr, b, ix)) [[unlikely]] { return; } if (!ensure_space(ctx, b, ix + key.size() + write_padding_bytes)) [[unlikely]] { return; } if constexpr (key.size() > 0) { std::memcpy(&b[ix], key.data(), key.size()); ix += key.size(); } decltype(auto) member = [&]() -> decltype(auto) { if constexpr (reflectable) { return get(t); } else { return get(reflect::values); } }(); serialize::op(get_member(value, member), ctx, b, ix); if constexpr (is_output_streaming) { flush_buffer(b, ix); } } }); } else { // Static path: use compile-time count for better performance if (!cbor_detail::encode_arg_cx()>(ctx, cbor::major::map, b, ix)) [[unlikely]] { return; } for_each([&]() { if (bool(ctx.error)) [[unlikely]] return; if constexpr (should_skip_field()) { return; } else { static constexpr sv key = reflect::keys[I]; // Write key as text string if (!cbor_detail::encode_arg_cx(ctx, cbor::major::tstr, b, ix)) [[unlikely]] { return; } if (!ensure_space(ctx, b, ix + key.size() + write_padding_bytes)) [[unlikely]] { return; } if constexpr (key.size() > 0) { std::memcpy(&b[ix], key.data(), key.size()); ix += key.size(); } decltype(auto) member = [&]() -> decltype(auto) { if constexpr (reflectable) { return get(t); } else { return get(reflect::values); } }(); serialize::op(get_member(value, member), ctx, b, ix); if constexpr (is_output_streaming) { flush_buffer(b, ix); } } }); } } }; // Tuples template requires(tuple_t || is_std_tuple) struct to final { template static void op(auto&& value, is_context auto&& ctx, auto&& b, auto& ix) { static constexpr auto N = glz::tuple_size_v; if (!cbor_detail::encode_arg_cx(ctx, cbor::major::array, b, ix)) [[unlikely]] { return; } if constexpr (is_std_tuple) { [&](std::index_sequence) { (serialize::op(std::get(value), ctx, b, ix), ...); }(std::make_index_sequence{}); } else { [&](std::index_sequence) { (serialize::op(glz::get(value), ctx, b, ix), ...); }(std::make_index_sequence{}); } } }; // Glaze arrays template requires glaze_array_t struct to final { template static void op(auto&& value, is_context auto&& ctx, auto&& b, auto& ix) { static constexpr auto N = reflect::size; if (!cbor_detail::encode_arg_cx(ctx, cbor::major::array, b, ix)) [[unlikely]] { return; } for_each([&]() { if (bool(ctx.error)) [[unlikely]] return; serialize::op(get_member(value, get(reflect::values)), ctx, b, ix); }); } }; // Expected types template struct to final { template GLZ_ALWAYS_INLINE static void op(auto&& value, is_context auto&& ctx, auto&& b, auto& ix) { if (value) { if constexpr (not std::is_void_v::value_type>) { serialize::op(*value, ctx, b, ix); } else { // void value type: serialize as empty map cbor_detail::encode_arg_cx<0>(ctx, cbor::major::map, b, ix); } } else { serialize::op(unexpected_wrapper{&value.error()}, ctx, b, ix); } } }; // Nullable types (std::optional, std::unique_ptr, std::shared_ptr) template struct to final { template GLZ_ALWAYS_INLINE static void op(auto&& value, is_context auto&& ctx, auto&& b, auto& ix) { if (value) { serialize::op(*value, ctx, b, ix); } else { cbor_detail::dump_byte(ctx, cbor::initial_byte(cbor::major::simple, cbor::simple::null_value), b, ix); } } }; // C-style arrays template requires(std::is_array_v) struct to { template GLZ_ALWAYS_INLINE static void op(const V (&value)[N], is_context auto&& ctx, auto&& b, auto& ix) { serialize::op(std::span{value, N}, ctx, b, ix); } }; // Variants template struct to final { template static void op(auto&& value, is_context auto&& ctx, auto&& b, auto& ix) { using Variant = std::decay_t; std::visit( [&](auto&& v) { using V = std::decay_t; static constexpr uint64_t index = [](std::index_sequence) { return ((std::is_same_v> * I) + ...); }(std::make_index_sequence>{}); // Encode variant as array [index, value] if (!cbor_detail::encode_arg_cx<2>(ctx, cbor::major::array, b, ix)) [[unlikely]] { return; } if (!cbor_detail::encode_arg(ctx, cbor::major::uint, index, b, ix)) [[unlikely]] { return; } serialize::op(v, ctx, b, ix); }, value); } }; // Glaze value wrapper template requires(glaze_value_t && !custom_write) struct to { template GLZ_ALWAYS_INLINE static void op(Value&& value, Ctx&& ctx, B&& b, IX&& ix) { using V = std::remove_cvref_t(), meta_wrapper_v))>; to::template op(get_member(std::forward(value), meta_wrapper_v), std::forward(ctx), std::forward(b), std::forward(ix)); } }; // Enums with glaze reflection template struct to final { template GLZ_ALWAYS_INLINE static void op(auto&& value, is_context auto&& ctx, auto&& b, auto& ix) { using V = std::underlying_type_t>; if constexpr (std::is_signed_v) { const auto v = static_cast(value); if (v >= 0) { cbor_detail::encode_arg(ctx, cbor::major::uint, static_cast(v), b, ix); } else { cbor_detail::encode_arg(ctx, cbor::major::nint, static_cast(~v), b, ix); } } else { cbor_detail::encode_arg(ctx, cbor::major::uint, static_cast(value), b, ix); } } }; // Plain enums (non-glaze) template requires(std::is_enum_v && !glaze_enum_t) struct to final { template GLZ_ALWAYS_INLINE static void op(auto&& value, is_context auto&& ctx, auto&& b, auto& ix) { using V = std::underlying_type_t>; if constexpr (std::is_signed_v) { const auto v = static_cast(value); if (v >= 0) { cbor_detail::encode_arg(ctx, cbor::major::uint, static_cast(v), b, ix); } else { cbor_detail::encode_arg(ctx, cbor::major::nint, static_cast(~v), b, ix); } } else { cbor_detail::encode_arg(ctx, cbor::major::uint, static_cast(value), b, ix); } } }; // Member function pointers (no-op) template struct to { template GLZ_ALWAYS_INLINE static void op(auto&&, is_context auto&&, auto&&, auto&&) noexcept {} }; // Includers (write as empty string) template struct to { template GLZ_ALWAYS_INLINE static void op(auto&&, is_context auto&& ctx, auto&& b, auto& ix) { cbor_detail::encode_arg_cx<0>(ctx, cbor::major::tstr, b, ix); } }; // Function type names template struct to final { template GLZ_ALWAYS_INLINE static void op(auto&& value, is_context auto&& ctx, auto&& b, auto& ix) { serialize::op(name_v>, ctx, b, ix); } }; // Raw JSON (write as text string) template struct to> final { template GLZ_ALWAYS_INLINE static void op(auto&& value, is_context auto&& ctx, auto&& b, auto& ix) { serialize::op(value.str, ctx, b, ix); } }; // Text (write as text string) template struct to> final { template GLZ_ALWAYS_INLINE static void op(auto&& value, is_context auto&& ctx, auto&& b, auto& ix) { serialize::op(value.str, ctx, b, ix); } }; // Nullable value types template requires(nullable_value_t && !nullable_like && !is_expected) struct to final { template GLZ_ALWAYS_INLINE static void op(auto&& value, is_context auto&& ctx, auto&& b, auto& ix) { if (value.has_value()) { serialize::op(value.value(), ctx, b, ix); } else { cbor_detail::dump_byte(ctx, cbor::initial_byte(cbor::major::simple, cbor::simple::null_value), b, ix); } } }; // std::chrono::duration - bare numeric count in the duration's native units template struct to final { template GLZ_ALWAYS_INLINE static void op(auto&& value, is_context auto&& ctx, auto&& b, auto& ix) { using Rep = typename std::remove_cvref_t::rep; to::template op(value.count(), ctx, b, ix); } }; // system_clock::time_point - tag 0 (RFC 3339 date/time string) template struct to final { template static void op(auto&& value, is_context auto&& ctx, auto&& b, auto& ix) { using namespace std::chrono; using TP = std::remove_cvref_t; using Duration = typename TP::duration; // Write tag 0 (RFC 3339 date/time string) if (!cbor_detail::encode_arg(ctx, cbor::major::tag, cbor::semantic_tag::datetime_string, b, ix)) [[unlikely]] { return; } constexpr size_t frac_digits = []() constexpr { using Period = typename Duration::period; if constexpr (std::ratio_greater_equal_v>) { return 0; } else if constexpr (std::ratio_greater_equal_v) { return 3; } else if constexpr (std::ratio_greater_equal_v) { return 6; } else { return 9; } }(); // "YYYY-MM-DDTHH:MM:SS[.fffffffff]Z" constexpr size_t str_len = 20 + (frac_digits > 0 ? 1 + frac_digits : 0); const auto dp = floor(value); const year_month_day ymd{dp}; const int yr = static_cast(ymd.year()); // RFC 3339 requires 4-digit year in [0000, 9999]. Reject out-of-range values // rather than silently corrupting the output with overflowed digits. if (yr < 0 || yr > 9999) [[unlikely]] { ctx.error = error_code::parse_error; return; } if (!cbor_detail::encode_arg_cx(ctx, cbor::major::tstr, b, ix)) [[unlikely]] { return; } if (!ensure_space(ctx, b, ix + str_len + write_padding_bytes)) [[unlikely]] { return; } const hh_mm_ss tod{floor(value - dp)}; const unsigned mo = static_cast(ymd.month()); const unsigned dy = static_cast(ymd.day()); const auto hr = static_cast(tod.hours().count()); const auto mi = static_cast(tod.minutes().count()); const auto sc = static_cast(tod.seconds().count()); auto write_digits = [&](uint64_t val) { for (size_t i = N; i > 0; --i) { b[ix + i - 1] = static_cast::value_type>('0' + val % 10); val /= 10; } ix += N; }; write_digits.template operator()<4>(static_cast(yr)); b[ix++] = '-'; write_digits.template operator()<2>(mo); b[ix++] = '-'; write_digits.template operator()<2>(dy); b[ix++] = 'T'; write_digits.template operator()<2>(hr); b[ix++] = ':'; write_digits.template operator()<2>(mi); b[ix++] = ':'; write_digits.template operator()<2>(sc); if constexpr (frac_digits > 0) { b[ix++] = '.'; const auto subsec = tod.subseconds(); if constexpr (frac_digits == 3) { write_digits.template operator()<3>(static_cast(duration_cast(subsec).count())); } else if constexpr (frac_digits == 6) { write_digits.template operator()<6>(static_cast(duration_cast(subsec).count())); } else { write_digits.template operator()<9>(static_cast(duration_cast(subsec).count())); } } b[ix++] = 'Z'; } }; // steady_clock::time_point - bare count in native duration (epoch is implementation-defined) template struct to final { template GLZ_ALWAYS_INLINE static void op(auto&& value, is_context auto&& ctx, auto&& b, auto& ix) { using Duration = typename std::remove_cvref_t::duration; using Rep = typename Duration::rep; to::template op(value.time_since_epoch().count(), ctx, b, ix); } }; // high_resolution_clock::time_point when it's a distinct type (rare) template struct to final { template GLZ_ALWAYS_INLINE static void op(auto&& value, is_context auto&& ctx, auto&& b, auto& ix) { using Duration = typename std::remove_cvref_t::duration; using Rep = typename Duration::rep; to::template op(value.time_since_epoch().count(), ctx, b, ix); } }; // epoch_time wrapper - RFC 8949 §3.4.2 tag 1 (epoch-based date/time). // Per §3.4.2 the tag 1 content MUST be an unsigned/negative integer (major types 0 or 1) // or a floating-point number (major type 7 with additional info 25/26/27). Nested tags // are forbidden, which rules out tag 4 decimal fractions. // Period >= 1 second: tag 1 + integer seconds (lossless) // Sub-second Period: tag 1 + float64 seconds // For nanosecond Durations, float64's 53-bit mantissa cannot hold a modern epoch's // nanosecond count exactly; round-trips have ~ULP-scale loss (~100 ns at year 2025). // Applications needing lossless nanosecond timestamps can use RFC 9581 tag 1001 in a // custom wrapper, or encode the nanosecond count as a bare integer type. template struct to> final { template static void op(auto&& wrapper, is_context auto&& ctx, auto&& b, auto& ix) { using namespace std::chrono; using Period = typename Duration::period; if (!cbor_detail::encode_arg(ctx, cbor::major::tag, cbor::semantic_tag::datetime_epoch, b, ix)) [[unlikely]] { return; } if constexpr (std::ratio_greater_equal_v>) { const auto secs = duration_cast(wrapper.value.time_since_epoch()).count(); to::template op(static_cast(secs), ctx, b, ix); } else { // To keep the double conversion lossless we need the source integer count // to fit within 2^53. wrapper.value's time_since_epoch() is in system_clock:: // duration units (often nanoseconds on Linux, microseconds on macOS); for a // modern epoch the nanosecond count is ~1.7e18, well beyond 2^53. Casting // through the coarser of Duration and system_clock::duration keeps the // integer count small enough to survive the conversion to double seconds. using sys_dur = std::chrono::system_clock::duration; if constexpr (std::ratio_greater_v) { const auto dur = duration_cast(wrapper.value.time_since_epoch()); const double secs = duration{dur}.count(); to::template op(secs, ctx, b, ix); } else { const double secs = duration{wrapper.value.time_since_epoch()}.count(); to::template op(secs, ctx, b, ix); } } } }; // ===== High-level write APIs ===== template T, class Buffer> [[nodiscard]] error_ctx write_cbor(T&& value, Buffer&& buffer) { return write()>(std::forward(value), std::forward(buffer)); } template T> [[nodiscard]] glz::expected write_cbor(T&& value) { return write()>(std::forward(value)); } template T> [[nodiscard]] inline error_code write_file_cbor(T&& value, const sv file_name, auto&& buffer) { const auto ec = write()>(std::forward(value), buffer); if (bool(ec)) [[unlikely]] { return ec.ec; } return buffer_to_file(buffer, file_name); } }