19 #include <seqan3/alphabet/detail/convert.hpp>
80 template <
typename stream_type,
81 typename seq_legal_alph_type,
82 typename ref_seqs_type,
83 typename ref_ids_type,
87 typename ref_seq_type,
89 typename ref_offset_type,
96 typename tag_dict_type,
97 typename e_value_type,
98 typename bit_score_type>
101 ref_seqs_type & ref_seqs,
107 ref_seq_type & SEQAN3_DOXYGEN_ONLY(
ref_seq),
111 cigar_type & cigar_vector,
115 tag_dict_type & tag_dict,
116 e_value_type & SEQAN3_DOXYGEN_ONLY(e_value),
117 bit_score_type & SEQAN3_DOXYGEN_ONLY(
bit_score));
119 template <
typename stream_type,
120 typename header_type,
123 typename ref_seq_type,
124 typename ref_id_type,
129 typename tag_dict_type>
132 [[maybe_unused]] header_type && header,
133 [[maybe_unused]] seq_type &&
seq,
134 [[maybe_unused]] qual_type &&
qual,
135 [[maybe_unused]] id_type &&
id,
136 [[maybe_unused]] int32_t
const offset,
137 [[maybe_unused]] ref_seq_type && SEQAN3_DOXYGEN_ONLY(
ref_seq),
138 [[maybe_unused]] ref_id_type &&
ref_id,
140 [[maybe_unused]] align_type && align,
141 [[maybe_unused]] cigar_type && cigar_vector,
143 [[maybe_unused]] uint8_t
const mapq,
144 [[maybe_unused]] mate_type &&
mate,
145 [[maybe_unused]] tag_dict_type && tag_dict,
146 [[maybe_unused]]
double SEQAN3_DOXYGEN_ONLY(e_value),
147 [[maybe_unused]]
double SEQAN3_DOXYGEN_ONLY(
bit_score));
151 bool header_was_read{
false};
157 struct alignment_record_core
162 uint32_t l_read_name:8;
165 uint32_t n_cigar_op:16;
183 ret[static_cast<index_t>(
'I')] = 1;
184 ret[static_cast<index_t>(
'D')] = 2;
185 ret[static_cast<index_t>(
'N')] = 3;
186 ret[static_cast<index_t>(
'S')] = 4;
187 ret[static_cast<index_t>(
'H')] = 5;
188 ret[static_cast<index_t>(
'P')] = 6;
189 ret[static_cast<index_t>(
'=')] = 7;
190 ret[static_cast<index_t>(
'X')] = 8;
197 static uint16_t reg2bin(int32_t beg, int32_t end) noexcept
200 if (beg >> 14 == end >> 14)
return ((1 << 15) - 1) / 7 + (beg >> 14);
201 if (beg >> 17 == end >> 17)
return ((1 << 12) - 1) / 7 + (beg >> 17);
202 if (beg >> 20 == end >> 20)
return ((1 << 9) - 1) / 7 + (beg >> 20);
203 if (beg >> 23 == end >> 23)
return ((1 << 6) - 1) / 7 + (beg >> 23);
204 if (beg >> 26 == end >> 26)
return ((1 << 3) - 1) / 7 + (beg >> 26);
208 using format_sam_base::read_field;
216 template <
typename stream_view_type, std::
integral number_type>
217 void read_field(stream_view_type && stream_view, number_type & target)
219 std::ranges::copy_n(std::ranges::begin(stream_view),
sizeof(target), reinterpret_cast<char *>(&target));
227 template <
typename stream_view_type>
228 void read_field(stream_view_type && stream_view,
float & target)
230 std::ranges::copy_n(std::ranges::begin(stream_view),
sizeof(int32_t), reinterpret_cast<char *>(&target));
243 template <
typename stream_view_type,
typename optional_value_type>
246 optional_value_type tmp;
247 read_field(std::forward<stream_view_type>(stream_view), tmp);
251 template <
typename stream_view_type,
typename value_type>
253 stream_view_type && stream_view,
254 value_type
const & SEQAN3_DOXYGEN_ONLY(value));
256 template <
typename stream_view_type>
257 void read_field(stream_view_type && stream_view, sam_tag_dictionary & target);
259 template <
typename cigar_input_type>
260 auto parse_binary_cigar(cigar_input_type && cigar_input, uint16_t n_cigar_op)
const;
262 static std::string get_tag_dict_str(sam_tag_dictionary
const & tag_dict);
266 template <
typename stream_type,
267 typename seq_legal_alph_type,
268 typename ref_seqs_type,
269 typename ref_ids_type,
272 typename offset_type,
273 typename ref_seq_type,
274 typename ref_id_type,
275 typename ref_offset_type,
282 typename tag_dict_type,
283 typename e_value_type,
284 typename bit_score_type>
287 ref_seqs_type & ref_seqs,
292 offset_type & offset,
293 ref_seq_type & SEQAN3_DOXYGEN_ONLY(ref_seq),
294 ref_id_type & ref_id,
295 ref_offset_type & ref_offset,
297 cigar_type & cigar_vector,
301 tag_dict_type & tag_dict,
302 e_value_type & SEQAN3_DOXYGEN_ONLY(e_value),
303 bit_score_type & SEQAN3_DOXYGEN_ONLY(bit_score))
305 static_assert(detail::decays_to_ignore_v<ref_offset_type> ||
306 detail::is_type_specialisation_of_v<ref_offset_type, std::optional>,
307 "The ref_offset must be a specialisation of std::optional.");
310 "The type of field::mapq must be uint8_t.");
313 "The type of field::flag must be seqan3::sam_flag.");
316 auto stream_view = std::ranges::subrange<decltype(stream_buf_t{stream}), decltype(stream_buf_t{})>
317 {stream_buf_t{stream}, stream_buf_t{}};
320 [[maybe_unused]] int32_t offset_tmp{};
321 [[maybe_unused]] int32_t soft_clipping_end{};
323 [[maybe_unused]] int32_t ref_length{0}, seq_length{0};
327 if (!header_was_read)
334 read_field(stream_view, tmp32);
343 read_field(stream_view, n_ref);
345 for (int32_t ref_idx = 0; ref_idx < n_ref; ++ref_idx)
347 read_field(stream_view, tmp32);
349 string_buffer.
resize(tmp32 - 1);
350 std::ranges::copy_n(std::ranges::begin(stream_view), tmp32 - 1, string_buffer.
data());
351 std::ranges::next(std::ranges::begin(stream_view));
353 read_field(stream_view, tmp32);
355 auto id_it = header.
ref_dict.find(string_buffer);
360 throw format_error{detail::to_string(
"Unknown reference name '" + string_buffer +
361 "' found in BAM file header (header.ref_ids():",
364 else if (id_it->second != ref_idx)
366 throw format_error{detail::to_string(
"Reference id '", string_buffer,
"' at position ", ref_idx,
367 " does not correspond to the position ", id_it->second,
368 " in the header (header.ref_ids():", header.
ref_ids(),
").")};
370 else if (std::get<0>(header.
ref_id_info[id_it->second]) != tmp32)
372 throw format_error{
"Provided reference has unequal length as specified in the header."};
376 header_was_read =
true;
378 if (stream_buf_t{stream} == stream_buf_t{})
384 alignment_record_core core;
385 std::ranges::copy_n(stream_view.begin(),
sizeof(core), reinterpret_cast<char *>(&core));
387 if (core.refID >= static_cast<int32_t>(header.
ref_ids().size()) || core.refID < -1)
389 throw format_error{detail::to_string(
"Reference id index '", core.refID,
"' is not in range of ",
390 "header.ref_ids(), which has size ", header.
ref_ids().size(),
".")};
392 else if (core.refID > -1)
403 if constexpr (!detail::decays_to_ignore_v<mate_type>)
405 if (core.next_refID > -1)
406 get<0>(
mate) = core.next_refID;
408 if (core.next_pos > -1)
409 get<1>(
mate) = core.next_pos;
411 get<2>(
mate) = core.tlen;
417 std::ranges::next(std::ranges::begin(stream_view));
421 if constexpr (!detail::decays_to_ignore_v<align_type> || !detail::decays_to_ignore_v<cigar_type>)
423 std::tie(tmp_cigar_vector, ref_length, seq_length) = parse_binary_cigar(stream_view, core.n_cigar_op);
424 transfer_soft_clipping_to(tmp_cigar_vector, offset_tmp, soft_clipping_end);
438 auto seq_stream = stream_view
446 if constexpr (detail::decays_to_ignore_v<seq_type>)
448 if constexpr (!detail::decays_to_ignore_v<align_type>)
451 "If you want to read ALIGNMENT but not SEQ, the alignment"
452 " object must store a sequence container at the second (query) position.");
454 if (!tmp_cigar_vector.empty())
456 assert(core.l_seq == (seq_length + offset_tmp + soft_clipping_end));
458 constexpr
auto from_dna16 = detail::convert_through_char_representation<alph_t, sam_dna16>;
460 get<1>(align).reserve(seq_length);
462 auto tmp_iter = std::ranges::begin(seq_stream);
463 std::ranges::advance(tmp_iter, offset_tmp / 2);
467 get<1>(align).push_back(from_dna16[
to_rank(get<1>(*tmp_iter))]);
472 for (
size_t i = (seq_length / 2); i > 0; --i)
474 get<1>(align).push_back(from_dna16[
to_rank(get<0>(*tmp_iter))]);
475 get<1>(align).push_back(from_dna16[
to_rank(get<1>(*tmp_iter))]);
481 get<1>(align).push_back(from_dna16[
to_rank(get<0>(*tmp_iter))]);
486 std::ranges::advance(tmp_iter, (soft_clipping_end + !(seq_length & 1)) / 2);
495 detail::consume(seq_stream);
497 std::ranges::next(std::ranges::begin(stream_view));
503 constexpr
auto from_dna16 = detail::convert_through_char_representation<alph_t, sam_dna16>;
505 for (
auto [d1, d2] : seq_stream)
515 std::ranges::next(std::ranges::begin(stream_view));
518 if constexpr (!detail::decays_to_ignore_v<align_type>)
522 std::ranges::distance(
seq) - soft_clipping_end));
534 int32_t remaining_bytes = core.block_size - (
sizeof(alignment_record_core) - 4) -
535 core.l_read_name - core.n_cigar_op * 4 - (core.l_seq + 1) / 2 - core.l_seq;
536 assert(remaining_bytes >= 0);
539 while (tags_view.size() > 0)
540 read_field(tags_view, tag_dict);
544 if constexpr (!detail::decays_to_ignore_v<align_type>)
549 if (core.l_seq != 0 && offset_tmp == core.l_seq)
551 if constexpr (detail::decays_to_ignore_v<tag_dict_type> | detail::decays_to_ignore_v<seq_type>)
553 throw format_error{detail::to_string(
"The cigar string '", offset_tmp,
"S", ref_length,
554 "N' suggests that the cigar string exceeded 65535 elements and was therefore ",
555 "stored in the optional field CG. You need to read in the field::tags and "
556 "field::seq in order to access this information.")};
560 auto it = tag_dict.find(
"CG"_tag);
562 if (it == tag_dict.end())
563 throw format_error{detail::to_string(
"The cigar string '", offset_tmp,
"S", ref_length,
564 "N' suggests that the cigar string exceeded 65535 elements and was therefore ",
565 "stored in the optional field CG but this tag is not present in the given ",
569 std::tie(tmp_cigar_vector, ref_length, seq_length) = parse_cigar(cigar_view);
570 offset_tmp = soft_clipping_end = 0;
571 transfer_soft_clipping_to(tmp_cigar_vector, offset_tmp, soft_clipping_end);
575 std::ranges::distance(
seq) - soft_clipping_end));
581 construct_alignment(align, tmp_cigar_vector, core.refID, ref_seqs, core.pos, ref_length);
584 if constexpr (!detail::decays_to_ignore_v<cigar_type>)
585 std::swap(cigar_vector, tmp_cigar_vector);
589 template <
typename stream_type,
590 typename header_type,
593 typename ref_seq_type,
594 typename ref_id_type,
599 typename tag_dict_type>
602 [[maybe_unused]] header_type && header,
603 [[maybe_unused]] seq_type &&
seq,
604 [[maybe_unused]] qual_type && qual,
605 [[maybe_unused]] id_type &&
id,
606 [[maybe_unused]] int32_t
const offset,
607 [[maybe_unused]] ref_seq_type && SEQAN3_DOXYGEN_ONLY(ref_seq),
608 [[maybe_unused]] ref_id_type && ref_id,
610 [[maybe_unused]] align_type && align,
611 [[maybe_unused]] cigar_type && cigar_vector,
612 [[maybe_unused]]
sam_flag const flag,
613 [[maybe_unused]] uint8_t
const mapq,
614 [[maybe_unused]] mate_type && mate,
615 [[maybe_unused]] tag_dict_type && tag_dict,
616 [[maybe_unused]]
double SEQAN3_DOXYGEN_ONLY(e_value),
617 [[maybe_unused]]
double SEQAN3_DOXYGEN_ONLY(bit_score))
622 static_assert((std::ranges::forward_range<seq_type> &&
624 "The seq object must be a std::ranges::forward_range over "
625 "letters that model seqan3::alphabet.");
627 static_assert((std::ranges::forward_range<id_type> &&
629 "The id object must be a std::ranges::forward_range over "
630 "letters that model seqan3::alphabet.");
632 static_assert((std::ranges::forward_range<ref_seq_type> &&
634 "The ref_seq object must be a std::ranges::forward_range "
635 "over letters that model seqan3::alphabet.");
637 if constexpr (!detail::decays_to_ignore_v<ref_id_type>)
639 static_assert((std::ranges::forward_range<ref_id_type> ||
642 "The ref_id object must be a std::ranges::forward_range "
643 "over letters that model seqan3::alphabet or an integral or a std::optional<integral>.");
647 "The align object must be a std::pair of two ranges whose "
648 "value_type is comparable to seqan3::gap");
653 "The align object must be a std::pair of two ranges whose "
654 "value_type is comparable to seqan3::gap");
656 static_assert((std::ranges::forward_range<qual_type> &&
658 "The qual object must be a std::ranges::forward_range "
659 "over letters that model seqan3::alphabet.");
662 "The mate object must be a std::tuple of size 3 with "
663 "1) a std::ranges::forward_range with a value_type modelling seqan3::alphabet, "
664 "2) a std::integral or std::optional<std::integral>, and "
665 "3) a std::integral.");
667 static_assert(((std::ranges::forward_range<decltype(std::get<0>(
mate))> ||
673 "The mate object must be a std::tuple of size 3 with "
674 "1) a std::ranges::forward_range with a value_type modelling seqan3::alphabet, "
675 "2) a std::integral or std::optional<std::integral>, and "
676 "3) a std::integral.");
679 "The tag_dict object must be of type seqan3::sam_tag_dictionary.");
681 if constexpr (detail::decays_to_ignore_v<header_type>)
683 throw format_error{
"BAM can only be written with a header but you did not provide enough information! "
684 "You can either construct the output file with ref_ids and ref_seqs information and "
685 "the header will be created for you, or you can access the `header` member directly."};
701 if (!header_was_written)
705 write_header(os, options, header);
706 int32_t l_text{static_cast<int32_t>(os.
str().size())};
707 std::ranges::copy_n(reinterpret_cast<char *>(&l_text), 4, stream_it);
711 int32_t n_ref{static_cast<int32_t>(header.
ref_ids().size())};
712 std::ranges::copy_n(reinterpret_cast<char *>(&n_ref), 4, stream_it);
714 for (int32_t ridx = 0; ridx < n_ref; ++ridx)
716 int32_t l_name{static_cast<int32_t>(header.
ref_ids()[ridx].size()) + 1};
717 std::ranges::copy_n(reinterpret_cast<char *>(&l_name), 4, stream_it);
719 std::ranges::copy(header.
ref_ids()[ridx].begin(), header.
ref_ids()[ridx].end(), stream_it);
722 std::ranges::copy_n(reinterpret_cast<char *>(&get<0>(header.
ref_id_info[ridx])), 4, stream_it);
725 header_was_written =
true;
731 int32_t ref_length{};
735 if (!std::ranges::empty(get<0>(align)) && !std::ranges::empty(get<1>(align)))
737 ref_length = std::ranges::distance(get<1>(align));
743 int32_t off_end{static_cast<int32_t>(std::ranges::distance(
seq)) -
offset};
745 for (
auto chr : get<1>(align))
749 off_end -= ref_length;
750 cigar_vector = detail::get_cigar_vector(align,
offset, off_end);
754 int32_t dummy_seq_length{};
755 for (
auto & [
count, operation] : cigar_vector)
756 update_alignment_lengths(ref_length, dummy_seq_length, operation.to_char(),
count);
759 if (cigar_vector.size() >= (1 << 16))
761 tag_dict[
"CG"_tag] = detail::get_cigar_string(cigar_vector);
762 cigar_vector.resize(2);
763 cigar_vector[0] =
cigar{static_cast<uint32_t>(std::ranges::distance(
seq)),
'S'_cigar_op};
764 cigar_vector[1] =
cigar{static_cast<uint32_t>(std::ranges::distance(get<1>(align))),
'N'_cigar_op};
767 std::string tag_dict_binary_str = get_tag_dict_str(tag_dict);
769 alignment_record_core core
774 std::max<uint8_t>(std::min<size_t>(std::ranges::distance(
id) + 1, 255), 2),
777 static_cast<uint16_t>(cigar_vector.size()),
779 static_cast<int32_t>(std::ranges::distance(
seq)),
781 get<1>(
mate).value_or(-1),
785 auto check_and_assign_id_to = [&header] ([[maybe_unused]]
auto & id_source,
786 [[maybe_unused]]
auto & id_target)
790 if constexpr (!detail::decays_to_ignore_v<id_t>)
794 id_target = id_source;
796 else if constexpr (detail::is_type_specialisation_of_v<id_t, std::optional>)
798 id_target = id_source.value_or(-1);
802 if (!std::ranges::empty(id_source))
806 if constexpr (std::ranges::contiguous_range<decltype(id_source)> &&
807 std::ranges::sized_range<decltype(id_source)> &&
810 id_it = header.
ref_dict.find(std::span{std::ranges::data(id_source),
818 "The ref_id type is not convertible to the reference id information stored in the "
819 "reference dictionary of the header object.");
821 id_it = header.
ref_dict.find(id_source);
826 throw format_error{detail::to_string(
"Unknown reference name '", id_source,
"' could "
827 "not be found in BAM header ref_dict: ",
831 id_target = id_it->second;
838 check_and_assign_id_to(
ref_id, core.refID);
841 check_and_assign_id_to(get<0>(
mate), core.next_refID);
844 core.block_size =
sizeof(core) - 4 +
846 core.n_cigar_op * 4 +
847 (core.l_seq + 1) / 2 +
849 tag_dict_binary_str.
size();
851 std::ranges::copy_n(reinterpret_cast<char *>(&core),
sizeof(core), stream_it);
853 if (std::ranges::distance(
id) == 0)
856 std::ranges::copy_n(std::ranges::begin(
id), core.l_read_name - 1, stream_it);
860 for (
auto [cigar_count, op] : cigar_vector)
862 cigar_count = cigar_count << 4;
863 cigar_count |= static_cast<int32_t>(char_to_sam_rank[op.to_char()]);
864 std::ranges::copy_n(reinterpret_cast<char *>(&cigar_count), 4, stream_it);
869 constexpr
auto to_dna16 = detail::convert_through_char_representation<sam_dna16, alph_t>;
871 auto sit = std::ranges::begin(
seq);
872 for (int32_t sidx = 0; sidx < ((core.l_seq & 1) ? core.l_seq - 1 : core.l_seq); ++sidx, ++sit)
877 stream_it = static_cast<char>(compressed_chr);
881 stream_it = static_cast<char>(
to_rank(to_dna16[
to_rank(*sit)]) << 4);
884 if (std::ranges::empty(
qual))
887 std::ranges::copy_n(v.begin(), core.l_seq, stream_it);
891 assert(static_cast<int32_t>(std::ranges::distance(
qual)) == core.l_seq);
893 std::ranges::copy_n(v.begin(), core.l_seq, stream_it);
897 stream << tag_dict_binary_str;
902 template <
typename stream_view_type,
typename value_type>
904 stream_view_type && stream_view,
905 value_type const & SEQAN3_DOXYGEN_ONLY(value))
908 read_field(stream_view,
count);
915 read_field(stream_view, tmp);
939 template <
typename stream_view_type>
940 inline void format_bam::read_field(stream_view_type && stream_view, sam_tag_dictionary & target)
948 uint16_t tag = static_cast<uint16_t>(*std::ranges::begin(stream_view)) << 8;
949 std::ranges::next(std::ranges::begin(stream_view));
950 tag += static_cast<uint16_t>(*std::ranges::begin(stream_view));
951 std::ranges::next(std::ranges::begin(stream_view));
952 char type_id = static_cast<char>(*std::ranges::begin(stream_view));
953 std::ranges::next(std::ranges::begin(stream_view));
959 target[tag] = static_cast<char>(*std::ranges::begin(stream_view));
960 std::ranges::next(std::ranges::begin(stream_view));
967 read_field(stream_view, tmp);
968 target[tag] = static_cast<int32_t>(tmp);
974 read_field(stream_view, tmp);
975 target[tag] = static_cast<int32_t>(tmp);
981 read_field(stream_view, tmp);
982 target[tag] = static_cast<int32_t>(tmp);
988 read_field(stream_view, tmp);
989 target[tag] = static_cast<int32_t>(tmp);
995 read_field(stream_view, tmp);
1002 read_field(stream_view, tmp);
1003 target[tag] = static_cast<int32_t>(tmp);
1009 read_field(stream_view, tmp);
1015 string_buffer.
clear();
1016 while (!is_char<'\0'>(*std::ranges::begin(stream_view)))
1018 string_buffer.
push_back(*std::ranges::begin(stream_view));
1019 std::ranges::next(std::ranges::begin(stream_view));
1021 std::ranges::next(std::ranges::begin(stream_view));
1022 target[tag] = string_buffer;
1032 char array_value_type_id = *std::ranges::begin(stream_view);
1033 std::ranges::next(std::ranges::begin(stream_view));
1035 switch (array_value_type_id)
1038 read_sam_dict_vector(target[tag], stream_view, int8_t{});
1041 read_sam_dict_vector(target[tag], stream_view, uint8_t{});
1044 read_sam_dict_vector(target[tag], stream_view, int16_t{});
1047 read_sam_dict_vector(target[tag], stream_view, uint16_t{});
1050 read_sam_dict_vector(target[tag], stream_view, int32_t{});
1053 read_sam_dict_vector(target[tag], stream_view, uint32_t{});
1056 read_sam_dict_vector(target[tag], stream_view,
float{});
1059 throw format_error{detail::to_string(
"The first character in the numerical id of a SAM tag ",
1060 "must be one of [cCsSiIf] but '", array_value_type_id,
"' was given.")};
1065 throw format_error{detail::to_string(
"The second character in the numerical id of a "
1066 "SAM tag must be one of [A,i,Z,H,B,f] but '", type_id,
"' was given.")};
1084 template <
typename cigar_input_type>
1085 inline auto format_bam::parse_binary_cigar(cigar_input_type && cigar_input, uint16_t n_cigar_op)
const
1088 char operation{
'\0'};
1090 int32_t ref_length{}, seq_length{};
1091 uint32_t operation_and_count{};
1092 constexpr
char const * cigar_mapping =
"MIDNSHP=X*******";
1093 constexpr uint32_t cigar_mask = 0x0f;
1095 if (n_cigar_op == 0)
1096 return std::tuple{operations, ref_length, seq_length};
1100 while (n_cigar_op > 0)
1102 std::ranges::copy_n(std::ranges::begin(cigar_input),
1103 sizeof(operation_and_count),
1104 reinterpret_cast<char*>(&operation_and_count));
1105 operation = cigar_mapping[operation_and_count & cigar_mask];
1106 count = operation_and_count >> 4;
1108 update_alignment_lengths(ref_length, seq_length, operation,
count);
1109 operations.emplace_back(
count, cigar_op{}.assign_char(operation));
1113 return std::tuple{operations, ref_length, seq_length};
1119 inline std::string format_bam::get_tag_dict_str(sam_tag_dictionary
const & tag_dict)
1123 auto stream_variant_fn = [&result] (
auto && arg)
1131 bool negative = arg < 0;
1132 auto n = __builtin_ctz(detail::next_power_of_two(((negative) ? arg * -1 : arg) + 1) >> 1) / 8;
1133 n = n * n + 2 * negative;
1139 result[result.size() - 1] =
'C';
1140 result.append(reinterpret_cast<char const *>(&arg), 1);
1145 result[result.size() - 1] =
'S';
1146 result.append(reinterpret_cast<char const *>(&arg), 2);
1151 result[result.size() - 1] =
'c';
1152 int8_t tmp = static_cast<int8_t>(arg);
1153 result.append(reinterpret_cast<char const *>(&tmp), 1);
1158 result[result.size() - 1] =
's';
1159 int16_t tmp = static_cast<int16_t>(arg);
1160 result.append(reinterpret_cast<char const *>(&tmp), 2);
1165 result.append(reinterpret_cast<char const *>(&arg), 4);
1172 result.append(reinterpret_cast<char const *>(arg.data()), arg.size() + 1);
1174 else if constexpr (!std::ranges::range<T>)
1176 result.append(reinterpret_cast<char const *>(&arg),
sizeof(arg));
1180 int32_t sz{static_cast<int32_t>(arg.size())};
1181 result.append(reinterpret_cast<char *>(&sz), 4);
1182 result.append(reinterpret_cast<char const *>(arg.data()), arg.size() *
sizeof(value_type_t<T>));
1186 for (
auto & [tag, variant] : tag_dict)
1188 result.push_back(static_cast<char>(tag / 256));
1189 result.push_back(static_cast<char>(tag % 256));
1191 result.push_back(detail::sam_tag_type_char[variant.
index()]);
1193 if (!is_char<'\0'>(detail::sam_tag_type_char_extra[variant.
index()]))
1194 result.push_back(detail::sam_tag_type_char_extra[variant.
index()]);