68 template <
typename stream_type,
69 typename seq_legal_alph_type,
70 typename ref_seqs_type,
71 typename ref_ids_type,
72 typename stream_pos_type,
75 typename ref_seq_type,
77 typename ref_offset_type,
83 typename tag_dict_type,
84 typename e_value_type,
85 typename bit_score_type>
88 ref_seqs_type & ref_seqs,
90 stream_pos_type & position_buffer,
94 ref_seq_type & SEQAN3_DOXYGEN_ONLY(
ref_seq),
97 cigar_type & cigar_vector,
101 tag_dict_type & tag_dict,
102 e_value_type & SEQAN3_DOXYGEN_ONLY(e_value),
103 bit_score_type & SEQAN3_DOXYGEN_ONLY(
bit_score));
105 template <
typename stream_type,
106 typename header_type,
109 typename ref_seq_type,
110 typename ref_id_type,
114 typename tag_dict_type>
117 [[maybe_unused]] header_type && header,
118 [[maybe_unused]] seq_type &&
seq,
119 [[maybe_unused]] qual_type &&
qual,
120 [[maybe_unused]] id_type &&
id,
121 [[maybe_unused]] ref_seq_type && SEQAN3_DOXYGEN_ONLY(
ref_seq),
122 [[maybe_unused]] ref_id_type &&
ref_id,
124 [[maybe_unused]] cigar_type && cigar_vector,
126 [[maybe_unused]] uint8_t
const mapq,
127 [[maybe_unused]] mate_type &&
mate,
128 [[maybe_unused]] tag_dict_type && tag_dict,
129 [[maybe_unused]]
double SEQAN3_DOXYGEN_ONLY(e_value),
130 [[maybe_unused]]
double SEQAN3_DOXYGEN_ONLY(
bit_score));
133 template <
typename stream_t,
typename header_type>
172 ret[
static_cast<index_t
>(
'I')] = 1;
173 ret[
static_cast<index_t
>(
'D')] = 2;
174 ret[
static_cast<index_t
>(
'N')] = 3;
175 ret[
static_cast<index_t
>(
'S')] = 4;
176 ret[
static_cast<index_t
>(
'H')] = 5;
177 ret[
static_cast<index_t
>(
'P')] = 6;
178 ret[
static_cast<index_t
>(
'=')] = 7;
179 ret[
static_cast<index_t
>(
'X')] = 8;
187 static uint16_t
reg2bin(int32_t beg, int32_t end)
noexcept
190 if (beg >> 14 == end >> 14)
191 return ((1 << 15) - 1) / 7 + (beg >> 14);
192 if (beg >> 17 == end >> 17)
193 return ((1 << 12) - 1) / 7 + (beg >> 17);
194 if (beg >> 20 == end >> 20)
195 return ((1 << 9) - 1) / 7 + (beg >> 20);
196 if (beg >> 23 == end >> 23)
197 return ((1 << 6) - 1) / 7 + (beg >> 23);
198 if (beg >> 26 == end >> 26)
199 return ((1 << 3) - 1) / 7 + (beg >> 26);
209 template <
typename stream_view_type, std::
integral number_type>
216 template <std::
integral number_type>
227 template <
typename stream_view_type>
233 template <
typename value_type>
236 value_type
const & SEQAN3_DOXYGEN_ONLY(value));
246template <
typename stream_type,
247 typename seq_legal_alph_type,
248 typename ref_seqs_type,
249 typename ref_ids_type,
250 typename stream_pos_type,
253 typename ref_seq_type,
254 typename ref_id_type,
255 typename ref_offset_type,
261 typename tag_dict_type,
262 typename e_value_type,
263 typename bit_score_type>
266 ref_seqs_type & ref_seqs,
268 stream_pos_type & position_buffer,
272 ref_seq_type & SEQAN3_DOXYGEN_ONLY(
ref_seq),
275 cigar_type & cigar_vector,
279 tag_dict_type & tag_dict,
280 e_value_type & SEQAN3_DOXYGEN_ONLY(e_value),
281 bit_score_type & SEQAN3_DOXYGEN_ONLY(
bit_score))
283 static_assert(detail::decays_to_ignore_v<ref_offset_type>
284 || detail::is_type_specialisation_of_v<ref_offset_type, std::optional>,
285 "The ref_offset must be a specialisation of std::optional.");
287 static_assert(detail::decays_to_ignore_v<mapq_type> || std::same_as<mapq_type, uint8_t>,
288 "The type of field::mapq must be uint8_t.");
290 static_assert(detail::decays_to_ignore_v<flag_type> || std::same_as<flag_type, sam_flag>,
291 "The type of field::flag must be seqan3::sam_flag.");
315 for (int32_t ref_idx = 0; ref_idx < n_ref; ++ref_idx)
327 if constexpr (detail::decays_to_ignore_v<ref_seqs_type>)
332 auto & reference_ids = header.
ref_ids();
338 header.
ref_dict.emplace(reference_ids.back(), reference_ids.size() - 1);
349 +
"' found in BAM file header (header.ref_ids():",
353 else if (id_it->second != ref_idx)
359 " does not correspond to the position ",
361 " in the header (header.ref_ids():",
365 else if (std::get<0>(header.
ref_id_info[id_it->second]) != l_ref)
367 throw format_error{
"Provided reference has unequal length as specified in the header."};
379 position_buffer = stream.tellg();
387 if (core.
refID >=
static_cast<int32_t
>(header.
ref_ids().size()) || core.
refID < -1)
391 "' is not in range of ",
392 "header.ref_ids(), which has size ",
396 else if (core.
refID > -1)
402 mapq =
static_cast<uint8_t
>(core.
mapq);
407 if constexpr (!detail::decays_to_ignore_v<mate_type>)
421 size_t considered_bytes{0};
423 if constexpr (!detail::decays_to_ignore_v<id_type>)
430 if constexpr (!detail::decays_to_ignore_v<cigar_type>)
437 if constexpr (!detail::decays_to_ignore_v<seq_type>)
439 size_t const number_of_bytes = (core.
l_seq + 1) / 2;
446 using alph_t = std::ranges::range_value_t<
decltype(
seq)>;
447 constexpr auto from_dna16 = detail::convert_through_char_representation<dna16sam, alph_t>;
450 for (
size_t i = 0, j = 0; i < number_of_bytes; ++i, j += 2)
460 considered_bytes += (core.
l_seq + 1) / 2;
464 if constexpr (!detail::decays_to_ignore_v<qual_type>)
469 for (int32_t i = 0; i < core.
l_seq; ++i)
470 qual[i] =
assign_char_to(
static_cast<char>(qual_str[i] + 33), std::ranges::range_value_t<qual_type>{});
473 considered_bytes += core.
l_seq;
477 if constexpr (!detail::decays_to_ignore_v<tag_dict_type>)
482 if constexpr (!detail::decays_to_ignore_v<cigar_type>)
489 if (core.
l_seq != 0 && sc_front == core.
l_seq)
491 if constexpr (detail::decays_to_ignore_v<tag_dict_type> | detail::decays_to_ignore_v<seq_type>)
496 "' suggests that the cigar string exceeded 65535 elements and was therefore ",
497 "stored in the optional field CG. You need to read in the field::tags and "
498 "field::seq in order to access this information.")};
502 auto it = tag_dict.
find(
"CG"_tag);
504 if (it == tag_dict.end())
508 "' suggests that the cigar string exceeded 65535 elements and was therefore ",
509 "stored in the optional field CG but this tag is not present in the given ",
520template <
typename stream_type,
521 typename header_type,
524 typename ref_seq_type,
525 typename ref_id_type,
529 typename tag_dict_type>
532 [[maybe_unused]] header_type && header,
533 [[maybe_unused]] seq_type &&
seq,
534 [[maybe_unused]] qual_type &&
qual,
535 [[maybe_unused]] id_type &&
id,
536 [[maybe_unused]] ref_seq_type && SEQAN3_DOXYGEN_ONLY(
ref_seq),
537 [[maybe_unused]] ref_id_type &&
ref_id,
539 [[maybe_unused]] cigar_type && cigar_vector,
541 [[maybe_unused]] uint8_t
const mapq,
542 [[maybe_unused]] mate_type &&
mate,
543 [[maybe_unused]] tag_dict_type && tag_dict,
544 [[maybe_unused]]
double SEQAN3_DOXYGEN_ONLY(e_value),
545 [[maybe_unused]]
double SEQAN3_DOXYGEN_ONLY(
bit_score))
551 "The seq object must be a std::ranges::forward_range over "
552 "letters that model seqan3::alphabet.");
555 "The id object must be a std::ranges::forward_range over "
556 "letters that model seqan3::alphabet.");
559 "The ref_seq object must be a std::ranges::forward_range "
560 "over letters that model seqan3::alphabet.");
562 if constexpr (!detail::decays_to_ignore_v<ref_id_type>)
564 static_assert((std::ranges::forward_range<ref_id_type> || std::integral<std::remove_reference_t<ref_id_type>>
565 || detail::is_type_specialisation_of_v<std::remove_cvref_t<ref_id_type>,
std::optional>),
566 "The ref_id object must be a std::ranges::forward_range "
567 "over letters that model seqan3::alphabet or an integral or a std::optional<integral>.");
571 "The qual object must be a std::ranges::forward_range "
572 "over letters that model seqan3::alphabet.");
575 "The mate object must be a std::tuple of size 3 with "
576 "1) a std::ranges::forward_range with a value_type modelling seqan3::alphabet, "
577 "2) a std::integral or std::optional<std::integral>, and "
578 "3) a std::integral.");
581 ((std::ranges::forward_range<decltype(std::get<0>(
mate))>
583 || detail::is_type_specialisation_of_v<
585 std::optional>)&&(std::integral<std::remove_cvref_t<decltype(std::get<1>(
mate))>>
586 || detail::is_type_specialisation_of_v<
588 std::optional>)&&std::integral<std::remove_cvref_t<decltype(std::get<2>(
mate))>>),
589 "The mate object must be a std::tuple of size 3 with "
590 "1) a std::ranges::forward_range with a value_type modelling seqan3::alphabet, "
591 "2) a std::integral or std::optional<std::integral>, and "
592 "3) a std::integral.");
595 "The tag_dict object must be of type seqan3::sam_tag_dictionary.");
597 if constexpr (detail::decays_to_ignore_v<header_type>)
599 throw format_error{
"BAM can only be written with a header but you did not provide enough information! "
600 "You can either construct the output file with reference names and reference length "
601 "information and the header will be created for you, or you can access the `header` member "
627 int32_t ref_length{};
630 if (!std::ranges::empty(cigar_vector))
632 int32_t dummy_seq_length{};
633 for (
auto & [count, operation] : cigar_vector)
637 if (cigar_vector.size() >= (1 << 16))
640 cigar_vector.resize(2);
641 cigar_vector[0] =
cigar{
static_cast<uint32_t
>(std::ranges::distance(
seq)),
'S'_cigar_operation};
642 cigar_vector[1] =
cigar{
static_cast<uint32_t
>(ref_length),
'N'_cigar_operation};
652 uint8_t read_name_size = std::min<uint8_t>(std::ranges::distance(
id), 254) + 1;
653 read_name_size +=
static_cast<uint8_t
>(read_name_size == 1);
661 static_cast<uint16_t
>(cigar_vector.size()),
663 static_cast<int32_t
>(std::ranges::distance(
seq)),
665 get<1>(
mate).value_or(-1),
668 auto check_and_assign_id_to = [&header]([[maybe_unused]]
auto & id_source, [[maybe_unused]]
auto & id_target)
672 if constexpr (!detail::decays_to_ignore_v<id_t>)
674 if constexpr (std::integral<id_t>)
676 id_target = id_source;
678 else if constexpr (detail::is_type_specialisation_of_v<id_t, std::optional>)
680 id_target = id_source.value_or(-1);
684 if (!std::ranges::empty(id_source))
688 if constexpr (std::ranges::contiguous_range<
decltype(id_source)>
689 && std::ranges::sized_range<
decltype(id_source)>
690 && std::ranges::borrowed_range<
decltype(id_source)>)
693 std::span{std::ranges::data(id_source), std::ranges::size(id_source)});
701 "The ref_id type is not convertible to the reference id information stored in the "
702 "reference dictionary of the header object.");
704 id_it = header.
ref_dict.find(id_source);
712 "not be found in BAM header ref_dict: ",
717 id_target = id_it->second;
724 check_and_assign_id_to(
ref_id, core.refID);
727 check_and_assign_id_to(get<0>(
mate), core.next_refID);
730 core.block_size =
sizeof(core) - 4 + core.l_read_name + core.n_cigar_op * 4
732 (core.l_seq + 1) / 2 +
734 tag_dict_binary_str.
size();
738 if (std::ranges::empty(
id))
745 for (
auto [cigar_count, op] : cigar_vector)
747 cigar_count = cigar_count << 4;
753 using alph_t = std::ranges::range_value_t<seq_type>;
754 constexpr auto to_dna16 = detail::convert_through_char_representation<alph_t, dna16sam>;
757 for (int32_t sidx = 0; sidx < ((core.l_seq & 1) ? core.l_seq - 1 : core.l_seq); ++sidx, ++sit)
762 stream_it =
static_cast<char>(compressed_chr);
766 stream_it =
static_cast<char>(
to_rank(to_dna16[
to_rank(*sit)]) << 4);
769 if (std::ranges::empty(
qual))
776 if (std::ranges::distance(
qual) != core.l_seq)
779 ". Got quality with size ",
780 std::ranges::distance(
qual),
784 | std::views::transform(
787 return static_cast<char>(
to_rank(chr));
793 stream << tag_dict_binary_str;
798template <
typename stream_t,
typename header_type>
801 if constexpr (detail::decays_to_ignore_v<header_type>)
803 throw format_error{
"BAM can only be written with a header but you did not provide enough information! "
804 "You can either construct the output file with reference names and reference length "
805 "information and the header will be created for you, or you can access the `header` member "
817#if SEQAN3_WORKAROUND_GCC_NO_CXX11_ABI
818 int32_t
const l_text{
static_cast<int32_t
>(os.
str().size())};
820 int32_t
const l_text{
static_cast<int32_t
>(os.view().size())};
824#if SEQAN3_WORKAROUND_GCC_NO_CXX11_ABI
825 auto header_view = os.
str();
827 auto header_view = os.view();
831 assert(header.ref_ids().size() < (1ull << 32));
832 int32_t
const n_ref{
static_cast<int32_t
>(header.ref_ids().size())};
835 for (int32_t ridx = 0; ridx < n_ref; ++ridx)
837 assert(header.ref_ids()[ridx].size() + 1 < (1ull << 32));
838 int32_t
const l_name{
static_cast<int32_t
>(header.ref_ids()[ridx].size()) + 1};
844 std::ranges::copy_n(
reinterpret_cast<char *
>(&get<0>(header.ref_id_info[ridx])), 4, stream_it);
867template <
typename value_type>
870 value_type
const & SEQAN3_DOXYGEN_ONLY(value))
872 auto it = str.
begin();
875 int32_t
const vector_size = [&]()
883 int32_t bytes_left{vector_size};
886 tmp_vector.
reserve(vector_size);
890 while (bytes_left > 0)
892 if constexpr (std::integral<value_type>)
894 else if constexpr (std::same_as<value_type, float>)
897 static_assert(std::is_same_v<value_type, void>,
"format_bam::read_sam_dict_vector: unsupported value_type");
904 variant = std::move(tmp_vector);
932 auto it = tag_str.
begin();
935 auto parse_integer_into_target = [&]<std::integral int_t>(uint16_t
const tag, int_t)
939 target[tag] =
static_cast<int32_t
>(tmp);
944 auto parse_array_into_target = [&]<
arithmetic array_value_t>(uint16_t
const tag, array_value_t)
947 it +=
sizeof(int32_t) +
sizeof(array_value_t) * count;
951 auto parse_tag = [&]()
953 uint16_t tag =
static_cast<uint16_t
>(*it) << 8;
955 tag |=
static_cast<uint16_t
>(*it);
960 while (it != tag_str.
end())
962 uint16_t
const tag = parse_tag();
964 char const type_id{*it};
978 parse_integer_into_target(tag, int8_t{});
983 parse_integer_into_target(tag, uint8_t{});
988 parse_integer_into_target(tag, int16_t{});
993 parse_integer_into_target(tag, uint16_t{});
998 parse_integer_into_target(tag, int32_t{});
1003 parse_integer_into_target(tag, uint32_t{});
1011 it +=
sizeof(float);
1016 std::string const v{
static_cast<char const *
>(it)};
1018 target[tag] = std::move(v);
1027 uint8_t dummy_byte{};
1029 if (str.
size() % 2 != 0)
1030 throw format_error{
"[CORRUPTED BAM FILE] Hexadecimal tag must have even number of digits."};
1034 for (
auto hex_begin = str.
begin(), hex_end = str.
begin() + 2; hex_begin != str.
end();
1035 hex_begin += 2, hex_end += 2)
1039 if (res.ec == std::errc::invalid_argument)
1041 +
std::string(hex_begin, hex_end) +
"' could not be cast into type uint8_t."};
1043 if (res.ec == std::errc::result_out_of_range)
1045 +
"' into type uint8_t would cause an overflow."};
1050 target[tag] = std::move(tmp_vector);
1052 it += str.
size() + 1;
1058 char array_value_type_id = *it;
1061 switch (array_value_type_id)
1064 parse_array_into_target(tag, int8_t{});
1067 parse_array_into_target(tag, uint8_t{});
1070 parse_array_into_target(tag, int16_t{});
1073 parse_array_into_target(tag, uint16_t{});
1076 parse_array_into_target(tag, int32_t{});
1079 parse_array_into_target(tag, uint32_t{});
1082 parse_array_into_target(tag,
float{});
1086 "must be one of [cCsSiIf] but '",
1087 array_value_type_id,
1094 "SAM tag must be one of [A,i,Z,H,B,f] but '",
1110 cigar_operation_mapping{
'M',
'I',
'D',
'N',
'S',
'H',
'P',
'=',
'X',
'*',
'*',
'*',
'*',
'*',
'*',
'*'};
1112 constexpr uint32_t cigar_operation_mask = 0x0f;
1115 char operation{
'\0'};
1117 uint32_t operation_and_count{};
1119 assert(cigar_str.
size() % 4 == 0);
1121 for (
auto it = cigar_str.
begin(); it != cigar_str.
end(); it +=
sizeof(operation_and_count))
1123 std::memcpy(&operation_and_count, it,
sizeof(operation_and_count));
1124 operation = cigar_operation_mapping[operation_and_count & cigar_operation_mask];
1125 count = operation_and_count >> 4;
1130 return cigar_vector;
1140 auto stream_variant_fn = [&result](
auto && arg)
1145 if constexpr (std::same_as<T, int32_t>)
1148 size_t const absolute_arg = std::abs(arg);
1150 bool const negative = arg < 0;
1151 n = n * n + 2 * negative;
1157 result[result.size() - 1] =
'C';
1158 result.append(
reinterpret_cast<char const *
>(&arg), 1);
1163 result[result.size() - 1] =
'S';
1164 result.append(
reinterpret_cast<char const *
>(&arg), 2);
1169 result[result.size() - 1] =
'c';
1170 int8_t tmp =
static_cast<int8_t
>(arg);
1171 result.append(
reinterpret_cast<char const *
>(&tmp), 1);
1176 result[result.size() - 1] =
's';
1177 int16_t tmp =
static_cast<int16_t
>(arg);
1178 result.append(
reinterpret_cast<char const *
>(&tmp), 2);
1183 result.append(
reinterpret_cast<char const *
>(&arg), 4);
1188 else if constexpr (std::same_as<T, std::string>)
1190 result.append(
reinterpret_cast<char const *
>(arg.data()), arg.size() + 1 );
1192 else if constexpr (!std::ranges::range<T>)
1194 result.append(
reinterpret_cast<char const *
>(&arg),
sizeof(arg));
1198 int32_t sz{
static_cast<int32_t
>(arg.size())};
1199 result.append(
reinterpret_cast<char *
>(&sz), 4);
1200 result.append(
reinterpret_cast<char const *
>(arg.data()),
1201 arg.size() *
sizeof(std::ranges::range_value_t<T>));
1205 for (
auto & [tag, variant] : tag_dict)
1207 result.push_back(
static_cast<char>(tag / 256));
1208 result.push_back(
static_cast<char>(tag % 256));
constexpr derived_type & assign_rank(rank_type const c) noexcept
Assign from a numeric value.
Definition alphabet_base.hpp:184
The seqan3::cigar semialphabet pairs a counter with a seqan3::cigar::operation letter.
Definition alphabet/cigar/cigar.hpp:57
Functionally the same as std::istreambuf_iterator, but faster.
Definition fast_istreambuf_iterator.hpp:37
Functionally the same as std::ostreambuf_iterator, but offers writing a range more efficiently.
Definition fast_ostreambuf_iterator.hpp:37
A 16 letter DNA alphabet, containing all IUPAC symbols minus the gap and plus an equality sign ('=').
Definition dna16sam.hpp:45
The actual implementation of seqan3::cigar::operation for documentation purposes only.
Definition cigar_operation.hpp:45
The SAM tag dictionary class that stores all optional SAM fields.
Definition sam_tag_dictionary.hpp:327
Provides seqan3::dna16sam.
T emplace_back(T... args)
Provides seqan3::detail::fast_ostreambuf_iterator.
constexpr auto assign_char_to
Assign a character to an alphabet object.
Definition alphabet/concept.hpp:521
constexpr auto assign_char_strictly_to
Assign a character to an alphabet object, throw if the character is not valid.
Definition alphabet/concept.hpp:731
constexpr auto to_rank
Return the rank representation of a (semi-)alphabet object.
Definition alphabet/concept.hpp:152
sam_flag
An enum flag that describes the properties of an aligned read (given as a SAM record).
Definition sam_flag.hpp:73
std::string get_cigar_string(std::vector< cigar > const &cigar_vector)
Transforms a vector of cigar elements into a string representation.
Definition io/sam_file/detail/cigar.hpp:118
constexpr char sam_tag_type_char_extra[12]
Each types SAM tag type extra char id. Index corresponds to the seqan3::detail::sam_tag_variant types...
Definition sam_tag_dictionary.hpp:42
void update_alignment_lengths(int32_t &ref_length, int32_t &seq_length, char const cigar_operation, uint32_t const cigar_count)
Updates the sequence lengths by cigar_count depending on the cigar operation op.
Definition io/sam_file/detail/cigar.hpp:48
constexpr char sam_tag_type_char[12]
Each SAM tag type char identifier. Index corresponds to the seqan3::detail::sam_tag_variant types.
Definition sam_tag_dictionary.hpp:39
constexpr std::vector< cigar > parse_cigar(std::string_view const cigar_str)
Parses a cigar string into a vector of operation-count pairs (e.g. (M, 3)).
Definition io/sam_file/detail/cigar.hpp:87
constexpr auto take_exactly_or_throw
A view adaptor that returns the first size elements from the underlying range and also exposes size i...
Definition take_exactly_view.hpp:587
constexpr auto istreambuf
A view factory that returns a view over the stream buffer of an input stream.
Definition istreambuf_view.hpp:104
@ flag
The alignment flag (bit information), uint16_t value.
@ ref_offset
Sequence (seqan3::field::ref_seq) relative start position (0-based), unsigned value.
@ ref_seq
The (reference) "sequence" information, usually a range of nucleotides or amino acids.
@ mapq
The mapping quality of the seqan3::field::seq alignment, usually a Phred-scaled score.
@ bit_score
The bit score (statistical significance indicator), unsigned value.
@ mate
The mate pair information given as a std::tuple of reference name, offset and template length.
@ ref_id
The identifier of the (reference) sequence that seqan3::field::seq was aligned to.
@ id
The identifier, usually a string.
@ seq
The "sequence", usually a range of nucleotides or amino acids.
@ qual
The qualities, usually in Phred score notation.
constexpr auto is_char
Checks whether a given letter is the same as the template non-type argument.
Definition predicate.hpp:60
constexpr auto repeat_n
A view factory that repeats a given value n times.
Definition repeat_n.hpp:88
The generic alphabet concept that covers most data types used in ranges.
A type that satisfies std::is_arithmetic_v<t>.
Checks whether from can be implicityly converted to to.
Whether a type behaves like a tuple.
Auxiliary functions for the SAM IO.
Provides seqan3::detail::istreambuf.
std::string to_string(value_type &&... values)
Streams all parameters via the seqan3::debug_stream and returns a concatenated string.
Definition to_string.hpp:26
The main SeqAn3 namespace.
Definition aligned_sequence_concept.hpp:26
Provides seqan3::debug_stream and related types.
Provides helper data structures for the seqan3::sam_file_output.
Provides the seqan3::sam_tag_dictionary class and auxiliaries.
Provides seqan3::views::slice.
The options type defines various option members that influence the behavior of all or some formats.
Definition sam_file/output_options.hpp:23
Provides seqan3::views::take_exactly and seqan3::views::take_exactly_or_throw.
Provides seqan3::debug_stream and related types.