From 584fafed96340b14253ffbcb7847652755ebbb9a Mon Sep 17 00:00:00 2001 From: Stephen Berry Date: Mon, 27 Apr 2026 17:45:39 -0500 Subject: [PATCH] std_tuple_protocol --- include/glaze/beve/read.hpp | 18 ++- include/glaze/beve/size.hpp | 14 +- include/glaze/beve/write.hpp | 14 +- include/glaze/cbor/read.hpp | 14 +- include/glaze/cbor/write.hpp | 14 +- include/glaze/eetf/read.hpp | 14 +- include/glaze/eetf/write.hpp | 14 +- include/glaze/json/flatten_map.hpp | 12 +- include/glaze/json/jmespath.hpp | 16 +- include/glaze/json/ndjson.hpp | 30 ++-- include/glaze/json/read.hpp | 17 +- include/glaze/json/schema.hpp | 6 +- include/glaze/json/write.hpp | 20 ++- include/glaze/jsonb/read.hpp | 17 +- include/glaze/jsonb/write.hpp | 14 +- include/glaze/msgpack/read.hpp | 14 +- include/glaze/msgpack/write.hpp | 14 +- include/glaze/toml/write.hpp | 20 ++- include/glaze/util/tuple.hpp | 21 +++ include/glaze/yaml/read.hpp | 16 +- include/glaze/yaml/write.hpp | 27 ++-- tests/json_test/CMakeLists.txt | 8 + tests/json_test/std_tuple_protocol_test.cpp | 164 ++++++++++++++++++++ 23 files changed, 400 insertions(+), 118 deletions(-) create mode 100644 tests/json_test/std_tuple_protocol_test.cpp diff --git a/include/glaze/beve/read.hpp b/include/glaze/beve/read.hpp index 4d77b62012..be9982c9d4 100644 --- a/include/glaze/beve/read.hpp +++ b/include/glaze/beve/read.hpp @@ -2096,7 +2096,7 @@ namespace glz }; template - requires(tuple_t || is_std_tuple) + requires(tuple_t || std_tuple_protocol) struct from final { template @@ -2113,17 +2113,23 @@ namespace glz ++it; using V = std::decay_t; - constexpr auto N = glz::tuple_size_v; + static constexpr bool use_std_protocol = std_tuple_protocol && !tuple_t; + constexpr auto N = []() constexpr { + if constexpr (use_std_protocol) + return std::tuple_size_v; + else + return glz::tuple_size_v; + }(); if constexpr (Opts.partial_read) { const auto n = int_from_compressed(ctx, it, end); if (bool(ctx.error)) [[unlikely]] { return; } - if constexpr (is_std_tuple) { + if constexpr (use_std_protocol) { for_each_short_circuit([&]() { if (I < n) { - parse::op(std::get(value), ctx, it, end); + parse::op(get(value), ctx, it, end); return false; // continue } return true; // short circuit @@ -2149,8 +2155,8 @@ namespace glz return; } - if constexpr (is_std_tuple) { - for_each([&]() { parse::op(std::get(value), ctx, it, end); }); + if constexpr (use_std_protocol) { + for_each([&]() { parse::op(get(value), ctx, it, end); }); } else { for_each([&]() { parse::op(glz::get(value), ctx, it, end); }); diff --git a/include/glaze/beve/size.hpp b/include/glaze/beve/size.hpp index cbc86c9e9b..4117ffebb7 100644 --- a/include/glaze/beve/size.hpp +++ b/include/glaze/beve/size.hpp @@ -788,20 +788,26 @@ namespace glz }; template - requires(tuple_t || is_std_tuple) + requires(tuple_t || std_tuple_protocol) struct calculate_size { template [[nodiscard]] static size_t op(auto&& value, size_t offset = 0) { - static constexpr auto N = glz::tuple_size_v; + static constexpr bool use_std_protocol = std_tuple_protocol && !tuple_t; + static constexpr auto N = []() constexpr { + if constexpr (use_std_protocol) + return std::tuple_size_v>; + else + return glz::tuple_size_v; + }(); size_t result = 1; // generic_array tag result += compressed_int_size(); // element count - if constexpr (is_std_tuple) { + if constexpr (use_std_protocol) { [&](std::index_sequence) { - ((result += calculate_size::template op(std::get(value), offset + result)), ...); + ((result += calculate_size::template op(get(value), offset + result)), ...); }(std::make_index_sequence{}); } else { diff --git a/include/glaze/beve/write.hpp b/include/glaze/beve/write.hpp index b577c1ef8c..9c6ec5fa5b 100644 --- a/include/glaze/beve/write.hpp +++ b/include/glaze/beve/write.hpp @@ -1582,7 +1582,7 @@ namespace glz }; template - requires(tuple_t || is_std_tuple) + requires(tuple_t || std_tuple_protocol) struct to final { template @@ -1593,15 +1593,21 @@ namespace glz } dump(b, ix); - static constexpr auto N = glz::tuple_size_v; + static constexpr bool use_std_protocol = std_tuple_protocol && !tuple_t; + static constexpr auto N = []() constexpr { + if constexpr (use_std_protocol) + return std::tuple_size_v>; + else + return glz::tuple_size_v; + }(); dump_compressed_int(ctx, b, ix); if (bool(ctx.error)) [[unlikely]] { return; } - if constexpr (is_std_tuple) { + if constexpr (use_std_protocol) { [&](std::index_sequence) { - ((serialize::op(std::get(value), ctx, b, ix), bool(ctx.error) ? void() : void()), ...); + ((serialize::op(get(value), ctx, b, ix), bool(ctx.error) ? void() : void()), ...); }(std::make_index_sequence{}); } else { diff --git a/include/glaze/cbor/read.hpp b/include/glaze/cbor/read.hpp index 6024b72484..5c0e587c75 100644 --- a/include/glaze/cbor/read.hpp +++ b/include/glaze/cbor/read.hpp @@ -1461,7 +1461,7 @@ namespace glz // Tuples template - requires(tuple_t || is_std_tuple) + requires(tuple_t || std_tuple_protocol) struct from final { template @@ -1487,7 +1487,13 @@ namespace glz } using V = std::decay_t; - static constexpr auto N = glz::tuple_size_v; + static constexpr bool use_std_protocol = std_tuple_protocol && !tuple_t; + static constexpr auto N = []() constexpr { + if constexpr (use_std_protocol) + return std::tuple_size_v; + else + return glz::tuple_size_v; + }(); uint64_t count = cbor_detail::decode_arg(ctx, it, end, additional_info); if (bool(ctx.error)) [[unlikely]] @@ -1498,8 +1504,8 @@ namespace glz return; } - if constexpr (is_std_tuple) { - for_each([&]() { parse::op(std::get(value), ctx, it, end); }); + if constexpr (use_std_protocol) { + for_each([&]() { parse::op(get(value), ctx, it, end); }); } else { for_each([&]() { parse::op(glz::get(value), ctx, it, end); }); diff --git a/include/glaze/cbor/write.hpp b/include/glaze/cbor/write.hpp index ec25bc452a..7a7184c809 100644 --- a/include/glaze/cbor/write.hpp +++ b/include/glaze/cbor/write.hpp @@ -875,20 +875,26 @@ namespace glz // Tuples template - requires(tuple_t || is_std_tuple) + requires(tuple_t || std_tuple_protocol) 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; + static constexpr bool use_std_protocol = std_tuple_protocol && !tuple_t; + static constexpr auto N = []() constexpr { + if constexpr (use_std_protocol) + return std::tuple_size_v>; + else + return glz::tuple_size_v; + }(); if (!cbor_detail::encode_arg_cx(ctx, cbor::major::array, b, ix)) [[unlikely]] { return; } - if constexpr (is_std_tuple) { + if constexpr (use_std_protocol) { [&](std::index_sequence) { - (serialize::op(std::get(value), ctx, b, ix), ...); + (serialize::op(get(value), ctx, b, ix), ...); }(std::make_index_sequence{}); } else { diff --git a/include/glaze/eetf/read.hpp b/include/glaze/eetf/read.hpp index 2751b5a29c..32fe180322 100644 --- a/include/glaze/eetf/read.hpp +++ b/include/glaze/eetf/read.hpp @@ -230,7 +230,7 @@ namespace glz }; template - requires(tuple_t || is_std_tuple) + requires(tuple_t || std_tuple_protocol) struct from final { template @@ -257,15 +257,21 @@ namespace glz it += index; using V = std::decay_t; - constexpr auto N = glz::tuple_size_v; + static constexpr bool use_std_protocol = std_tuple_protocol && !tuple_t; + constexpr auto N = []() constexpr { + if constexpr (use_std_protocol) + return std::tuple_size_v; + else + return glz::tuple_size_v; + }(); if (fields_count != N) { ctx.error = error_code::syntax_error; return; } - if constexpr (is_std_tuple) { - for_each([&]() { parse::op(std::get(value), ctx, it, end); }); + if constexpr (use_std_protocol) { + for_each([&]() { parse::op(get(value), ctx, it, end); }); } else { for_each([&]() { parse::op(glz::get(value), ctx, it, end); }); diff --git a/include/glaze/eetf/write.hpp b/include/glaze/eetf/write.hpp index cdb9f2a068..26b290c45d 100644 --- a/include/glaze/eetf/write.hpp +++ b/include/glaze/eetf/write.hpp @@ -127,7 +127,7 @@ namespace glz }; template - requires(tuple_t || is_std_tuple) + requires(tuple_t || std_tuple_protocol) struct to final { template @@ -142,16 +142,22 @@ namespace glz requires(check_no_header(Opts)) GLZ_ALWAYS_INLINE static void op(auto&& value, Ctx&& ctx, Args&&... args) noexcept { - static constexpr auto N = glz::tuple_size_v; + static constexpr bool use_std_protocol = std_tuple_protocol && !tuple_t; + static constexpr auto N = []() constexpr { + if constexpr (use_std_protocol) + return std::tuple_size_v>; + else + return glz::tuple_size_v; + }(); encode_tuple_header(N, ctx, std::forward(args)...); if (bool(ctx.error)) [[unlikely]] { return; } - if constexpr (is_std_tuple) { + if constexpr (use_std_protocol) { [&](std::index_sequence) { - (serialize::op(std::get(value), ctx, args...), ...); + (serialize::op(get(value), ctx, args...), ...); }(std::make_index_sequence{}); } else { diff --git a/include/glaze/json/flatten_map.hpp b/include/glaze/json/flatten_map.hpp index 78e3606bca..963199628c 100644 --- a/include/glaze/json/flatten_map.hpp +++ b/include/glaze/json/flatten_map.hpp @@ -65,13 +65,13 @@ namespace glz write_flatten_map_elements(get_member(value, glz::get(meta_v)), ctx, b, ix, first); }); } - else if constexpr (is_std_tuple) { - static constexpr auto N = glz::tuple_size_v; + else if constexpr (std_tuple_protocol && !tuple_t) { + static constexpr auto N = std::tuple_size_v; for_each([&]() { if (bool(ctx.error)) [[unlikely]] { return; } - write_flatten_map_elements(std::get(value), ctx, b, ix, first); + write_flatten_map_elements(get(value), ctx, b, ix, first); }); } else if constexpr (tuple_t) { @@ -177,13 +177,13 @@ namespace glz read_flatten_map_elements(get_member(value, glz::get(meta_v)), ctx, it, end, first); }); } - else if constexpr (is_std_tuple) { - static constexpr auto N = glz::tuple_size_v; + else if constexpr (std_tuple_protocol && !tuple_t) { + static constexpr auto N = std::tuple_size_v; for_each([&]() { if (bool(ctx.error)) [[unlikely]] { return; } - read_flatten_map_elements(std::get(value), ctx, it, end, first); + read_flatten_map_elements(get(value), ctx, it, end, first); }); } else if constexpr (tuple_t) { diff --git a/include/glaze/json/jmespath.hpp b/include/glaze/json/jmespath.hpp index 9de5674158..6e8a0d7283 100644 --- a/include/glaze/json/jmespath.hpp +++ b/include/glaze/json/jmespath.hpp @@ -425,14 +425,14 @@ namespace glz { template requires(Opts.format == JSON && not readable_array_t && - not(tuple_t> || is_std_tuple>)) + not(tuple_t> || std_tuple_protocol>)) inline void handle_slice(const jmespath::ArrayParseResult&, T&&, context& ctx, auto&&, auto&&) { ctx.error = error_code::syntax_error; } template - requires(Opts.format == JSON && (tuple_t> || is_std_tuple>)) + requires(Opts.format == JSON && (tuple_t> || std_tuple_protocol>)) inline void handle_slice(const jmespath::ArrayParseResult& decomposed_key, T&& value, context& ctx, auto&& it, auto end) { @@ -462,7 +462,13 @@ namespace glz // Iterate tuple elements using TupleType = std::decay_t; - constexpr size_t N = glz::tuple_size_v; + static constexpr bool use_std_protocol = std_tuple_protocol && !tuple_t; + constexpr size_t N = []() constexpr { + if constexpr (use_std_protocol) + return std::tuple_size_v; + else + return glz::tuple_size_v; + }(); for_each([&]() { if (bool(ctx.error)) return; @@ -499,8 +505,8 @@ namespace glz return; } - if constexpr (is_std_tuple) { - parse::template op(std::get(value), ctx, it, end); + if constexpr (use_std_protocol) { + parse::template op(get(value), ctx, it, end); } else { parse::template op(glz::get(value), ctx, it, end); diff --git a/include/glaze/json/ndjson.hpp b/include/glaze/json/ndjson.hpp index aea424c0da..53dae00f86 100644 --- a/include/glaze/json/ndjson.hpp +++ b/include/glaze/json/ndjson.hpp @@ -106,7 +106,7 @@ namespace glz }; template - requires glaze_array_t || tuple_t || is_std_tuple + requires glaze_array_t || tuple_t || std_tuple_protocol struct from { template @@ -116,10 +116,14 @@ namespace glz return; } + static constexpr bool use_std_protocol = std_tuple_protocol && !tuple_t; static constexpr auto N = []() constexpr { if constexpr (glaze_array_t) { return reflect::size; } + else if constexpr (use_std_protocol) { + return std::tuple_size_v; + } else { return glz::tuple_size_v; } @@ -148,8 +152,8 @@ namespace glz if constexpr (I != 0) { read_new_lines(); } - if constexpr (is_std_tuple) { - parse::op(std::get(value), ctx, it, end); + if constexpr (use_std_protocol) { + parse::op(get(value), ctx, it, end); } else if constexpr (glaze_array_t) { parse::op(get_member(value, glz::get(meta_v)), ctx, it, end); @@ -223,29 +227,17 @@ namespace glz }; template - requires is_std_tuple> + requires(std_tuple_protocol> && !tuple_t> && !glaze_array_t>) struct to { template static void op(auto&& value, is_context auto&& ctx, Args&&... args) { - static constexpr auto N = []() constexpr { - if constexpr (glaze_array_t>) { - return glz::tuple_size_v>>; - } - else { - return glz::tuple_size_v>; - } - }(); - using V = std::decay_t; + static constexpr auto N = std::tuple_size_v; + for_each([&]() { - if constexpr (glaze_array_t) { - serialize::op(value.*std::get(meta_v), ctx, std::forward(args)...); - } - else { - serialize::op(std::get(value), ctx, std::forward(args)...); - } + serialize::op(get(value), ctx, std::forward(args)...); constexpr bool needs_new_line = I < N - 1; if constexpr (needs_new_line) { dump('\n', std::forward(args)...); diff --git a/include/glaze/json/read.hpp b/include/glaze/json/read.hpp index c1f3a68dfc..85749e0124 100644 --- a/include/glaze/json/read.hpp +++ b/include/glaze/json/read.hpp @@ -2642,16 +2642,20 @@ namespace glz }; template - requires glaze_array_t || tuple_t || is_std_tuple + requires glaze_array_t || tuple_t || std_tuple_protocol struct from { template static void op(auto& value, is_context auto&& ctx, auto&& it, auto end) { + static constexpr bool use_std_protocol = std_tuple_protocol && !tuple_t; static constexpr auto N = []() constexpr { if constexpr (glaze_array_t) { return reflect::size; } + else if constexpr (use_std_protocol) { + return std::tuple_size_v; + } else { return glz::tuple_size_v; } @@ -2691,8 +2695,8 @@ namespace glz return true; } } - if constexpr (is_std_tuple) { - parse::op()>(std::get(value), ctx, it, end); + if constexpr (use_std_protocol) { + parse::op()>(get(value), ctx, it, end); if (bool(ctx.error)) [[unlikely]] return true; } @@ -3452,7 +3456,8 @@ namespace glz concept variant_object_type = json_object; template - concept variant_array_type = array_t> || glaze_array_t || tuple_t || is_std_tuple; + concept variant_array_type = + array_t> || glaze_array_t || tuple_t || std_tuple_protocol; template concept variant_null_type = null_t; @@ -3624,8 +3629,8 @@ namespace glz found_match = true; } else if constexpr (not Options.null_terminated) { - constexpr bool is_complete_type = - num_t || bool_t || string_t || std::is_enum_v || tuple_t || is_std_tuple; + constexpr bool is_complete_type = num_t || bool_t || string_t || std::is_enum_v || + tuple_t || std_tuple_protocol; if constexpr (is_complete_type) { if (ctx.error == error_code::end_reached && it > copy_it) { diff --git a/include/glaze/json/schema.hpp b/include/glaze/json/schema.hpp index a556b83058..bb0bbe216f 100644 --- a/include/glaze/json/schema.hpp +++ b/include/glaze/json/schema.hpp @@ -849,18 +849,22 @@ namespace glz }; template - requires glaze_array_t> || tuple_t> || is_std_tuple> + requires glaze_array_t> || tuple_t> || std_tuple_protocol> struct to_json_schema { template static void op(auto& s, auto& defs) { using V = std::decay_t; + static constexpr bool use_std_protocol = std_tuple_protocol && !tuple_t; s.type = sv{"array"}; static constexpr auto N = []() constexpr { if constexpr (glaze_array_t) { return glz::tuple_size_v>; } + else if constexpr (use_std_protocol) { + return std::tuple_size_v; + } else { return glz::tuple_size_v; } diff --git a/include/glaze/json/write.hpp b/include/glaze/json/write.hpp index 8fd5b8d465..c766812546 100644 --- a/include/glaze/json/write.hpp +++ b/include/glaze/json/write.hpp @@ -1854,18 +1854,23 @@ namespace glz }; template - requires glaze_array_t || tuple_t> || is_std_tuple + requires glaze_array_t || tuple_t> || std_tuple_protocol> struct to { template static void op(auto&& value, is_context auto&& ctx, Args&&... args) { + using V = std::decay_t; + static constexpr bool use_std_protocol = std_tuple_protocol && !tuple_t; static constexpr auto N = []() constexpr { - if constexpr (glaze_array_t>) { - return glz::tuple_size_v>>; + if constexpr (glaze_array_t) { + return glz::tuple_size_v>; + } + else if constexpr (use_std_protocol) { + return std::tuple_size_v; } else { - return glz::tuple_size_v>; + return glz::tuple_size_v; } }(); @@ -1876,14 +1881,13 @@ namespace glz dump_newline_indent(check_indentation_char(Opts), ctx.depth, args...); } } - using V = std::decay_t; for_each([&]() { if constexpr (glaze_array_t) { serialize::op(get_member(value, glz::get(meta_v)), ctx, args...); } - else if constexpr (is_std_tuple) { - using Value = core_t(value))>; - to::template op(std::get(value), ctx, args...); + else if constexpr (use_std_protocol) { + using Value = core_t(value))>; + to::template op(get(value), ctx, args...); } else { using Value = core_t(value))>; diff --git a/include/glaze/jsonb/read.hpp b/include/glaze/jsonb/read.hpp index 183676002d..33487a9bef 100644 --- a/include/glaze/jsonb/read.hpp +++ b/include/glaze/jsonb/read.hpp @@ -738,7 +738,7 @@ namespace glz // Tuples template - requires(tuple_t || is_std_tuple) + requires(tuple_t || std_tuple_protocol) struct from final { template @@ -759,11 +759,18 @@ namespace glz } const auto stop = it + sz; - static constexpr auto N = glz::tuple_size_v>; - if constexpr (is_std_tuple) { + using V = std::decay_t; + static constexpr bool use_std_protocol = std_tuple_protocol && !tuple_t; + static constexpr auto N = []() constexpr { + if constexpr (use_std_protocol) + return std::tuple_size_v; + else + return glz::tuple_size_v; + }(); + if constexpr (use_std_protocol) { for_each([&]() { if (bool(ctx.error)) return; - parse::op(std::get(value), ctx, it, end); + parse::op(get(value), ctx, it, end); }); } else { @@ -1019,7 +1026,7 @@ namespace glz {}; template struct is_jsonb_variant_array : std::bool_constant || readable_array_t || tuple_t || - is_std_tuple || glaze_array_t> + std_tuple_protocol || glaze_array_t> {}; template struct is_jsonb_variant_object : std::bool_constant || readable_map_t || pair_t || diff --git a/include/glaze/jsonb/write.hpp b/include/glaze/jsonb/write.hpp index 60e27d276b..746756d396 100644 --- a/include/glaze/jsonb/write.hpp +++ b/include/glaze/jsonb/write.hpp @@ -246,7 +246,7 @@ namespace glz // Tuples template - requires(tuple_t || is_std_tuple) + requires(tuple_t || std_tuple_protocol) struct to final { template @@ -258,10 +258,16 @@ namespace glz } const size_t payload_start = ix; - static constexpr auto N = glz::tuple_size_v; - if constexpr (is_std_tuple) { + static constexpr bool use_std_protocol = std_tuple_protocol && !tuple_t; + static constexpr auto N = []() constexpr { + if constexpr (use_std_protocol) + return std::tuple_size_v>; + else + return glz::tuple_size_v; + }(); + if constexpr (use_std_protocol) { [&](std::index_sequence) { - (serialize::op(std::get(value), ctx, b, ix), ...); + (serialize::op(get(value), ctx, b, ix), ...); }(std::make_index_sequence{}); } else { diff --git a/include/glaze/msgpack/read.hpp b/include/glaze/msgpack/read.hpp index ca21657fb2..01ff1ce992 100644 --- a/include/glaze/msgpack/read.hpp +++ b/include/glaze/msgpack/read.hpp @@ -976,7 +976,7 @@ namespace glz }; template - requires(tuple_t || is_std_tuple) + requires(tuple_t || std_tuple_protocol) struct from { template @@ -986,14 +986,20 @@ namespace glz if (!msgpack::read_array_length(ctx, tag, it, end, len)) { return; } - static constexpr auto N = glz::tuple_size_v; + static constexpr bool use_std_protocol = std_tuple_protocol && !tuple_t; + static constexpr auto N = []() constexpr { + if constexpr (use_std_protocol) + return std::tuple_size_v>; + else + return glz::tuple_size_v; + }(); if (len != N) { ctx.error = error_code::syntax_error; return; } - if constexpr (is_std_tuple) { + if constexpr (use_std_protocol) { [&](std::index_sequence) { - (parse::template op(std::get(value), ctx, it, end), ...); + (parse::template op(get(value), ctx, it, end), ...); }(std::make_index_sequence{}); } else { diff --git a/include/glaze/msgpack/write.hpp b/include/glaze/msgpack/write.hpp index 1935a7b5c3..f54f5a005a 100644 --- a/include/glaze/msgpack/write.hpp +++ b/include/glaze/msgpack/write.hpp @@ -1003,20 +1003,26 @@ namespace glz }; template - requires(tuple_t || is_std_tuple) + requires(tuple_t || std_tuple_protocol) struct to { template GLZ_ALWAYS_INLINE static void op(Value&& value, Ctx&& ctx, B&& b, IX&& ix) { - static constexpr auto N = glz::tuple_size_v; + static constexpr bool use_std_protocol = std_tuple_protocol && !tuple_t; + static constexpr auto N = []() constexpr { + if constexpr (use_std_protocol) + return std::tuple_size_v>; + else + return glz::tuple_size_v; + }(); if (!msgpack::detail::write_array_header(ctx, N, b, ix)) [[unlikely]] { return; } - if constexpr (is_std_tuple) { + if constexpr (use_std_protocol) { [&](std::index_sequence) { ((void)(bool(ctx.error) ? void() - : (serialize::op(std::get(value), ctx, b, ix), void())), + : (serialize::op(get(value), ctx, b, ix), void())), ...); }(std::make_index_sequence{}); } diff --git a/include/glaze/toml/write.hpp b/include/glaze/toml/write.hpp index b45cc87055..d9fcf3d528 100644 --- a/include/glaze/toml/write.hpp +++ b/include/glaze/toml/write.hpp @@ -1283,18 +1283,23 @@ namespace glz }; template - requires glaze_array_t || tuple_t> || is_std_tuple + requires glaze_array_t || tuple_t> || std_tuple_protocol> struct to { template static void op(auto&& value, is_context auto&& ctx, B&& b, auto& ix) { + using V = std::decay_t; + static constexpr bool use_std_protocol = std_tuple_protocol && !tuple_t; static constexpr auto N = []() constexpr { - if constexpr (glaze_array_t>) { - return glz::tuple_size_v>>; + if constexpr (glaze_array_t) { + return glz::tuple_size_v>; + } + else if constexpr (use_std_protocol) { + return std::tuple_size_v; } else { - return glz::tuple_size_v>; + return glz::tuple_size_v; } }(); @@ -1302,7 +1307,6 @@ namespace glz return; } dump('[', b, ix); - using V = std::decay_t; for_each([&]() { if (bool(ctx.error)) [[unlikely]] { return; @@ -1310,9 +1314,9 @@ namespace glz if constexpr (glaze_array_t) { serialize::op(get_member(value, glz::get(meta_v)), ctx, b, ix); } - else if constexpr (is_std_tuple) { - using Value = core_t(value))>; - to::template op(std::get(value), ctx, b, ix); + else if constexpr (use_std_protocol) { + using Value = core_t(value))>; + to::template op(get(value), ctx, b, ix); } else { using Value = core_t(value))>; diff --git a/include/glaze/util/tuple.hpp b/include/glaze/util/tuple.hpp index b56bee40dd..048bf2f9dc 100644 --- a/include/glaze/util/tuple.hpp +++ b/include/glaze/util/tuple.hpp @@ -5,6 +5,7 @@ #include +#include "glaze/concepts/container_concepts.hpp" #include "glaze/reflection/get_name.hpp" #include "glaze/tuplet/tuple.hpp" #include "glaze/util/for_each.hpp" @@ -15,6 +16,26 @@ namespace glz template concept is_std_tuple = is_specialization_v; + namespace detail + { + // Unqualified `get<0>(t)` resolves via ADL: std types are found in namespace std, + // user types via their own associated namespaces. Matches the structured-bindings + // tuple-protocol lookup rule ([dcl.struct.bind]). + template + concept has_adl_tuple_get = requires(T&& t) { get<0>(static_cast(t)); }; + } + + // True for any non-pair, non-range type that satisfies the standard tuple protocol: + // std::tuple_size::value is well-formed, std::tuple_element<0, T>::type is + // well-formed, and ADL get<0>(t) is callable. This includes std::tuple as well + // as user-defined types that opt in by specializing std::tuple_size / + // std::tuple_element and providing an ADL `get`. + template + concept std_tuple_protocol = requires { + std::tuple_size>::value; + typename std::tuple_element<0, std::remove_cvref_t>::type; + } && detail::has_adl_tuple_get && !pair_t && !range; + // TODO: This doesn't appear to be used. Should it be removed? template concept is_schema_class = requires { diff --git a/include/glaze/yaml/read.hpp b/include/glaze/yaml/read.hpp index 76c9c8c1d2..65bb0fb479 100644 --- a/include/glaze/yaml/read.hpp +++ b/include/glaze/yaml/read.hpp @@ -3709,7 +3709,7 @@ namespace glz // Tuples (std::tuple, glaze_array_t, tuple_t) template - requires(glaze_array_t || tuple_t || is_std_tuple) + requires(glaze_array_t || tuple_t || std_tuple_protocol) struct from { template @@ -3718,10 +3718,14 @@ namespace glz if (bool(ctx.error)) [[unlikely]] return; + static constexpr bool use_std_protocol = std_tuple_protocol && !tuple_t; static constexpr auto N = []() constexpr { if constexpr (glaze_array_t) { return reflect::size; } + else if constexpr (use_std_protocol) { + return std::tuple_size_v; + } else { return glz::tuple_size_v; } @@ -3778,9 +3782,9 @@ namespace glz yaml::skip_ws_and_newlines(it, end); } - if constexpr (is_std_tuple) { + if constexpr (use_std_protocol) { using element_t = std::tuple_element_t>; - from::template op()>(std::get(value), ctx, it, end); + from::template op()>(get(value), ctx, it, end); } else if constexpr (glaze_array_t) { using element_t = std::decay_t(meta_v)))>; @@ -3841,9 +3845,9 @@ namespace glz ( [&]() { if (I == index) { - if constexpr (is_std_tuple) { + if constexpr (use_std_protocol) { using element_t = std::tuple_element_t>; - from::template op(std::get(value), ctx, it, end); + from::template op(get(value), ctx, it, end); } else if constexpr (glaze_array_t) { using element_t = std::decay_t(meta_v)))>; @@ -4665,7 +4669,7 @@ namespace glz template concept yaml_variant_array_type = - array_t> || glaze_array_t || tuple_t || is_std_tuple; + array_t> || glaze_array_t || tuple_t || std_tuple_protocol; template concept yaml_variant_null_type = null_t; diff --git a/include/glaze/yaml/write.hpp b/include/glaze/yaml/write.hpp index 366e285076..7305d809df 100644 --- a/include/glaze/yaml/write.hpp +++ b/include/glaze/yaml/write.hpp @@ -725,18 +725,23 @@ namespace glz // Tuples (std::tuple, glaze_array_t, tuple_t) template - requires(glaze_array_t || tuple_t> || is_std_tuple) + requires(glaze_array_t || tuple_t> || std_tuple_protocol>) struct to { template static void op(auto&& value, is_context auto&& ctx, B&& b, auto& ix) { + using V = std::decay_t; + static constexpr bool use_std_protocol = std_tuple_protocol && !tuple_t; static constexpr auto N = []() constexpr { - if constexpr (glaze_array_t>) { - return glz::tuple_size_v>>; + if constexpr (glaze_array_t) { + return glz::tuple_size_v>; + } + else if constexpr (use_std_protocol) { + return std::tuple_size_v; } else { - return glz::tuple_size_v>; + return glz::tuple_size_v; } }(); @@ -747,7 +752,6 @@ namespace glz } dump('[', b, ix); - using V = std::decay_t; for_each([&]() { if (bool(ctx.error)) [[unlikely]] return; @@ -760,9 +764,9 @@ namespace glz serialize::op()>(get_member(value, glz::get(meta_v)), ctx, b, ix); } - else if constexpr (is_std_tuple) { - using element_t = core_t(value))>; - to::template op()>(std::get(value), ctx, b, ix); + else if constexpr (use_std_protocol) { + using element_t = core_t(value))>; + to::template op()>(get(value), ctx, b, ix); } else { using element_t = core_t(value))>; @@ -780,7 +784,6 @@ namespace glz indent_level = ctx.indent_level; } - using V = std::decay_t; for_each([&]() { if (bool(ctx.error)) [[unlikely]] return; @@ -806,15 +809,15 @@ namespace glz serialize::op(get_member(value, glz::get(meta_v)), ctx, b, ix); } } - else if constexpr (is_std_tuple) { + else if constexpr (use_std_protocol) { using element_t = std::decay_t>>; if constexpr (yaml::is_simple_type()) { - to::template op(std::get(value), ctx, b, ix); + to::template op(get(value), ctx, b, ix); dump('\n', b, ix); } else { dump('\n', b, ix); - to::template op(std::get(value), ctx, b, ix); + to::template op(get(value), ctx, b, ix); } } else { diff --git a/tests/json_test/CMakeLists.txt b/tests/json_test/CMakeLists.txt index b96c75263e..de6edc0fb8 100644 --- a/tests/json_test/CMakeLists.txt +++ b/tests/json_test/CMakeLists.txt @@ -175,3 +175,11 @@ add_executable(non_aggregate_reflection_test non_aggregate_reflection_test.cpp) target_link_libraries(non_aggregate_reflection_test PRIVATE glz_test_common) add_test(NAME non_aggregate_reflection_test COMMAND non_aggregate_reflection_test) + +## Standard tuple protocol test - discussion #2530 + +add_executable(std_tuple_protocol_test std_tuple_protocol_test.cpp) + +target_link_libraries(std_tuple_protocol_test PRIVATE glz_test_common) + +add_test(NAME std_tuple_protocol_test COMMAND std_tuple_protocol_test) diff --git a/tests/json_test/std_tuple_protocol_test.cpp b/tests/json_test/std_tuple_protocol_test.cpp new file mode 100644 index 0000000000..387e3f0852 --- /dev/null +++ b/tests/json_test/std_tuple_protocol_test.cpp @@ -0,0 +1,164 @@ +// Glaze Library +// Test that user-defined types implementing the standard C++ tuple protocol +// (std::tuple_size, std::tuple_element, ADL get) are treated as tuples +// without requiring duplicate glz:: specializations. +// See https://github.com/stephenberry/glaze/discussions/2530 + +#include +#include +#include +#include + +#include "glaze/glaze.hpp" +#include "glaze/cbor.hpp" +#include "glaze/msgpack.hpp" +#include "ut/ut.hpp" + +using namespace ut; + +namespace user_ns +{ + class MyTuple + { + public: + MyTuple() = default; + MyTuple(int x, double y, std::string z) : x_(x), y_(y), z_(std::move(z)) {} + + int& x() & { return x_; } + double& y() & { return y_; } + std::string& z() & { return z_; } + + const int& x() const& { return x_; } + const double& y() const& { return y_; } + const std::string& z() const& { return z_; } + + bool operator==(const MyTuple&) const = default; + + private: + int x_{}; + double y_{}; + std::string z_{}; + }; + + // ADL get — found by unqualified name lookup in the same namespace as MyTuple. + template + decltype(auto) get(MyTuple& t) + { + if constexpr (I == 0) + return t.x(); + else if constexpr (I == 1) + return t.y(); + else + return t.z(); + } + + template + decltype(auto) get(const MyTuple& t) + { + if constexpr (I == 0) + return t.x(); + else if constexpr (I == 1) + return t.y(); + else + return t.z(); + } + + template + decltype(auto) get(MyTuple&& t) + { + if constexpr (I == 0) + return std::move(t).x(); + else if constexpr (I == 1) + return std::move(t).y(); + else + return std::move(t).z(); + } +} + +// Standard tuple protocol customization: specializing these in namespace std +// is explicitly permitted by the standard for program-defined types. +template <> +struct std::tuple_size : std::integral_constant +{}; + +template <> +struct std::tuple_element<0, user_ns::MyTuple> +{ + using type = int; +}; + +template <> +struct std::tuple_element<1, user_ns::MyTuple> +{ + using type = double; +}; + +template <> +struct std::tuple_element<2, user_ns::MyTuple> +{ + using type = std::string; +}; + +suite std_tuple_protocol = [] { + "concept detects std-protocol types"_test = [] { + static_assert(glz::std_tuple_protocol); + static_assert(glz::std_tuple_protocol>); + static_assert(not glz::std_tuple_protocol>); // pair has its own path + static_assert(not glz::std_tuple_protocol>); // array goes through range path + static_assert(not glz::std_tuple_protocol); + }; + + "json roundtrip via std tuple protocol"_test = [] { + user_ns::MyTuple t{42, 3.14, "answer"}; + std::string buffer; + expect(not glz::write_json(t, buffer)); + expect(buffer == R"([42,3.14,"answer"])"); + + user_ns::MyTuple t2; + expect(glz::read_json(t2, buffer) == glz::error_code::none); + expect(t == t2); + }; + + "std::tuple still roundtrips"_test = [] { + auto t = std::make_tuple(1, 2.5, std::string("ok")); + decltype(t) t2; + std::string buffer; + expect(not glz::write_json(t, buffer)); + expect(glz::read_json(t2, buffer) == glz::error_code::none); + expect(t == t2); + }; + + "std::pair stays a pair (object), not a tuple (array)"_test = [] { + std::pair p{"key", 9}; + std::string buffer; + expect(not glz::write_json(p, buffer)); + // pair_t serializer writes {"key":9}, not ["key",9] + expect(buffer == R"({"key":9})"); + }; + + "beve roundtrip"_test = [] { + user_ns::MyTuple t{42, 3.14, "answer"}; + auto buffer = glz::write_beve(t).value_or(std::string{}); + user_ns::MyTuple t2; + expect(glz::read_beve(t2, buffer) == glz::error_code::none); + expect(t == t2); + }; + + "cbor roundtrip"_test = [] { + user_ns::MyTuple t{42, 3.14, "answer"}; + auto buffer = glz::write_cbor(t).value_or(std::string{}); + user_ns::MyTuple t2; + expect(glz::read_cbor(t2, buffer) == glz::error_code::none); + expect(t == t2); + }; + + "msgpack roundtrip"_test = [] { + user_ns::MyTuple t{42, 3.14, "answer"}; + auto buffer = glz::write_msgpack(t).value_or(std::string{}); + user_ns::MyTuple t2; + expect(glz::read_msgpack(t2, buffer) == glz::error_code::none); + expect(t == t2); + }; +}; + +int main() { return 0; }