67 template <
typename stream_type,
68 typename seq_legal_alph_type,
69 typename ref_seqs_type,
70 typename ref_ids_type,
71 typename stream_pos_type,
74 typename ref_seq_type,
76 typename ref_offset_type,
82 typename tag_dict_type,
83 typename e_value_type,
84 typename bit_score_type>
87 ref_seqs_type & ref_seqs,
89 stream_pos_type & position_buffer,
93 ref_seq_type & SEQAN3_DOXYGEN_ONLY(
ref_seq),
96 cigar_type & cigar_vector,
100 tag_dict_type & tag_dict,
101 e_value_type & SEQAN3_DOXYGEN_ONLY(e_value),
102 bit_score_type & SEQAN3_DOXYGEN_ONLY(
bit_score));
104 template <
typename stream_type,
105 typename header_type,
108 typename ref_seq_type,
109 typename ref_id_type,
113 typename tag_dict_type>
116 [[maybe_unused]] header_type && header,
117 [[maybe_unused]] seq_type &&
seq,
118 [[maybe_unused]] qual_type &&
qual,
119 [[maybe_unused]] id_type &&
id,
120 [[maybe_unused]] ref_seq_type && SEQAN3_DOXYGEN_ONLY(
ref_seq),
121 [[maybe_unused]] ref_id_type &&
ref_id,
123 [[maybe_unused]] cigar_type && cigar_vector,
125 [[maybe_unused]] uint8_t
const mapq,
126 [[maybe_unused]] mate_type &&
mate,
127 [[maybe_unused]] tag_dict_type && tag_dict,
128 [[maybe_unused]]
double SEQAN3_DOXYGEN_ONLY(e_value),
129 [[maybe_unused]]
double SEQAN3_DOXYGEN_ONLY(
bit_score));
132 template <
typename stream_t,
typename header_type>
171 ret[
static_cast<index_t
>(
'I')] = 1;
172 ret[
static_cast<index_t
>(
'D')] = 2;
173 ret[
static_cast<index_t
>(
'N')] = 3;
174 ret[
static_cast<index_t
>(
'S')] = 4;
175 ret[
static_cast<index_t
>(
'H')] = 5;
176 ret[
static_cast<index_t
>(
'P')] = 6;
177 ret[
static_cast<index_t
>(
'=')] = 7;
178 ret[
static_cast<index_t
>(
'X')] = 8;
186 static uint16_t
reg2bin(int32_t beg, int32_t end)
noexcept
189 if (beg >> 14 == end >> 14)
190 return ((1 << 15) - 1) / 7 + (beg >> 14);
191 if (beg >> 17 == end >> 17)
192 return ((1 << 12) - 1) / 7 + (beg >> 17);
193 if (beg >> 20 == end >> 20)
194 return ((1 << 9) - 1) / 7 + (beg >> 20);
195 if (beg >> 23 == end >> 23)
196 return ((1 << 6) - 1) / 7 + (beg >> 23);
197 if (beg >> 26 == end >> 26)
198 return ((1 << 3) - 1) / 7 + (beg >> 26);
208 template <
typename stream_view_type, std::
integral number_type>
215 template <std::
integral number_type>
226 template <
typename stream_view_type>
232 template <
typename value_type>
235 value_type
const & SEQAN3_DOXYGEN_ONLY(value));
245template <
typename stream_type,
246 typename seq_legal_alph_type,
247 typename ref_seqs_type,
248 typename ref_ids_type,
249 typename stream_pos_type,
252 typename ref_seq_type,
253 typename ref_id_type,
254 typename ref_offset_type,
260 typename tag_dict_type,
261 typename e_value_type,
262 typename bit_score_type>
265 ref_seqs_type & ref_seqs,
267 stream_pos_type & position_buffer,
271 ref_seq_type & SEQAN3_DOXYGEN_ONLY(
ref_seq),
274 cigar_type & cigar_vector,
278 tag_dict_type & tag_dict,
279 e_value_type & SEQAN3_DOXYGEN_ONLY(e_value),
280 bit_score_type & SEQAN3_DOXYGEN_ONLY(
bit_score))
282 static_assert(detail::decays_to_ignore_v<ref_offset_type>
283 || detail::is_type_specialisation_of_v<ref_offset_type, std::optional>,
284 "The ref_offset must be a specialisation of std::optional.");
286 static_assert(detail::decays_to_ignore_v<mapq_type> || std::same_as<mapq_type, uint8_t>,
287 "The type of field::mapq must be uint8_t.");
289 static_assert(detail::decays_to_ignore_v<flag_type> || std::same_as<flag_type, sam_flag>,
290 "The type of field::flag must be seqan3::sam_flag.");
314 for (int32_t ref_idx = 0; ref_idx < n_ref; ++ref_idx)
326 if constexpr (detail::decays_to_ignore_v<ref_seqs_type>)
331 auto & reference_ids = header.
ref_ids();
337 header.
ref_dict.emplace(reference_ids.back(), reference_ids.size() - 1);
348 +
"' found in BAM file header (header.ref_ids():",
352 else if (id_it->second != ref_idx)
358 " does not correspond to the position ",
360 " in the header (header.ref_ids():",
364 else if (std::get<0>(header.
ref_id_info[id_it->second]) != l_ref)
366 throw format_error{
"Provided reference has unequal length as specified in the header."};
378 position_buffer = stream.tellg();
386 if (core.
refID >=
static_cast<int32_t
>(header.
ref_ids().size()) || core.
refID < -1)
390 "' is not in range of ",
391 "header.ref_ids(), which has size ",
395 else if (core.
refID > -1)
401 mapq =
static_cast<uint8_t
>(core.
mapq);
406 if constexpr (!detail::decays_to_ignore_v<mate_type>)
420 size_t considered_bytes{0};
422 if constexpr (!detail::decays_to_ignore_v<id_type>)
429 if constexpr (!detail::decays_to_ignore_v<cigar_type>)
436 if constexpr (!detail::decays_to_ignore_v<seq_type>)
438 size_t const number_of_bytes = (core.
l_seq + 1) / 2;
445 using alph_t = std::ranges::range_value_t<
decltype(
seq)>;
446 constexpr auto from_dna16 = detail::convert_through_char_representation<dna16sam, alph_t>;
449 for (
size_t i = 0, j = 0; i < number_of_bytes; ++i, j += 2)
459 considered_bytes += (core.
l_seq + 1) / 2;
463 if constexpr (!detail::decays_to_ignore_v<qual_type>)
468 for (int32_t i = 0; i < core.
l_seq; ++i)
469 qual[i] =
assign_char_to(
static_cast<char>(qual_str[i] + 33), std::ranges::range_value_t<qual_type>{});
472 considered_bytes += core.
l_seq;
476 if constexpr (!detail::decays_to_ignore_v<tag_dict_type>)
481 if constexpr (!detail::decays_to_ignore_v<cigar_type>)
488 if (core.
l_seq != 0 && sc_front == core.
l_seq)
490 if constexpr (detail::decays_to_ignore_v<tag_dict_type> | detail::decays_to_ignore_v<seq_type>)
495 "' suggests that the cigar string exceeded 65535 elements and was therefore ",
496 "stored in the optional field CG. You need to read in the field::tags and "
497 "field::seq in order to access this information.")};
501 auto it = tag_dict.
find(
"CG"_tag);
503 if (it == tag_dict.end())
507 "' suggests that the cigar string exceeded 65535 elements and was therefore ",
508 "stored in the optional field CG but this tag is not present in the given ",
519template <
typename stream_type,
520 typename header_type,
523 typename ref_seq_type,
524 typename ref_id_type,
528 typename tag_dict_type>
531 [[maybe_unused]] header_type && header,
532 [[maybe_unused]] seq_type &&
seq,
533 [[maybe_unused]] qual_type &&
qual,
534 [[maybe_unused]] id_type &&
id,
535 [[maybe_unused]] ref_seq_type && SEQAN3_DOXYGEN_ONLY(
ref_seq),
536 [[maybe_unused]] ref_id_type &&
ref_id,
538 [[maybe_unused]] cigar_type && cigar_vector,
540 [[maybe_unused]] uint8_t
const mapq,
541 [[maybe_unused]] mate_type &&
mate,
542 [[maybe_unused]] tag_dict_type && tag_dict,
543 [[maybe_unused]]
double SEQAN3_DOXYGEN_ONLY(e_value),
544 [[maybe_unused]]
double SEQAN3_DOXYGEN_ONLY(
bit_score))
550 "The seq object must be a std::ranges::forward_range over "
551 "letters that model seqan3::alphabet.");
554 "The id object must be a std::ranges::forward_range over "
555 "letters that model seqan3::alphabet.");
558 "The ref_seq object must be a std::ranges::forward_range "
559 "over letters that model seqan3::alphabet.");
561 if constexpr (!detail::decays_to_ignore_v<ref_id_type>)
563 static_assert((std::ranges::forward_range<ref_id_type> || std::integral<std::remove_reference_t<ref_id_type>>
564 || detail::is_type_specialisation_of_v<std::remove_cvref_t<ref_id_type>,
std::optional>),
565 "The ref_id object must be a std::ranges::forward_range "
566 "over letters that model seqan3::alphabet or an integral or a std::optional<integral>.");
570 "The qual object must be a std::ranges::forward_range "
571 "over letters that model seqan3::alphabet.");
574 "The mate object must be a std::tuple of size 3 with "
575 "1) a std::ranges::forward_range with a value_type modelling seqan3::alphabet, "
576 "2) a std::integral or std::optional<std::integral>, and "
577 "3) a std::integral.");
580 ((std::ranges::forward_range<decltype(std::get<0>(
mate))>
582 || detail::is_type_specialisation_of_v<
584 std::optional>)&&(std::integral<std::remove_cvref_t<decltype(std::get<1>(
mate))>>
585 || detail::is_type_specialisation_of_v<
587 std::optional>)&&std::integral<std::remove_cvref_t<decltype(std::get<2>(
mate))>>),
588 "The mate object must be a std::tuple of size 3 with "
589 "1) a std::ranges::forward_range with a value_type modelling seqan3::alphabet, "
590 "2) a std::integral or std::optional<std::integral>, and "
591 "3) a std::integral.");
594 "The tag_dict object must be of type seqan3::sam_tag_dictionary.");
596 if constexpr (detail::decays_to_ignore_v<header_type>)
598 throw format_error{
"BAM can only be written with a header but you did not provide enough information! "
599 "You can either construct the output file with reference names and reference length "
600 "information and the header will be created for you, or you can access the `header` member "
626 int32_t ref_length{};
629 if (!std::ranges::empty(cigar_vector))
631 int32_t dummy_seq_length{};
632 for (
auto & [count, operation] : cigar_vector)
636 if (cigar_vector.size() >= (1 << 16))
639 cigar_vector.resize(2);
640 cigar_vector[0] =
cigar{
static_cast<uint32_t
>(std::ranges::distance(
seq)),
'S'_cigar_operation};
641 cigar_vector[1] =
cigar{
static_cast<uint32_t
>(ref_length),
'N'_cigar_operation};
651 uint8_t read_name_size = std::min<uint8_t>(std::ranges::distance(
id), 254) + 1;
652 read_name_size +=
static_cast<uint8_t
>(read_name_size == 1);
660 static_cast<uint16_t
>(cigar_vector.size()),
662 static_cast<int32_t
>(std::ranges::distance(
seq)),
664 get<1>(
mate).value_or(-1),
667 auto check_and_assign_id_to = [&header]([[maybe_unused]]
auto & id_source, [[maybe_unused]]
auto & id_target)
671 if constexpr (!detail::decays_to_ignore_v<id_t>)
673 if constexpr (std::integral<id_t>)
675 id_target = id_source;
677 else if constexpr (detail::is_type_specialisation_of_v<id_t, std::optional>)
679 id_target = id_source.value_or(-1);
683 if (!std::ranges::empty(id_source))
687 if constexpr (std::ranges::contiguous_range<
decltype(id_source)>
688 && std::ranges::sized_range<
decltype(id_source)>
689 && std::ranges::borrowed_range<
decltype(id_source)>)
692 std::span{std::ranges::data(id_source), std::ranges::size(id_source)});
700 "The ref_id type is not convertible to the reference id information stored in the "
701 "reference dictionary of the header object.");
703 id_it = header.
ref_dict.find(id_source);
711 "not be found in BAM header ref_dict: ",
716 id_target = id_it->second;
723 check_and_assign_id_to(
ref_id, core.refID);
726 check_and_assign_id_to(get<0>(
mate), core.next_refID);
729 core.block_size =
sizeof(core) - 4 + core.l_read_name + core.n_cigar_op * 4
731 (core.l_seq + 1) / 2 +
733 tag_dict_binary_str.
size();
737 if (std::ranges::empty(
id))
744 for (
auto [cigar_count, op] : cigar_vector)
746 cigar_count = cigar_count << 4;
752 using alph_t = std::ranges::range_value_t<seq_type>;
753 constexpr auto to_dna16 = detail::convert_through_char_representation<alph_t, dna16sam>;
756 for (int32_t sidx = 0; sidx < ((core.l_seq & 1) ? core.l_seq - 1 : core.l_seq); ++sidx, ++sit)
761 stream_it =
static_cast<char>(compressed_chr);
765 stream_it =
static_cast<char>(
to_rank(to_dna16[
to_rank(*sit)]) << 4);
768 if (std::ranges::empty(
qual))
775 if (std::ranges::distance(
qual) != core.l_seq)
778 ". Got quality with size ",
779 std::ranges::distance(
qual),
783 | std::views::transform(
786 return static_cast<char>(
to_rank(chr));
792 stream << tag_dict_binary_str;
797template <
typename stream_t,
typename header_type>
800 if constexpr (detail::decays_to_ignore_v<header_type>)
802 throw format_error{
"BAM can only be written with a header but you did not provide enough information! "
803 "You can either construct the output file with reference names and reference length "
804 "information and the header will be created for you, or you can access the `header` member "
816#if SEQAN3_WORKAROUND_GCC_NO_CXX11_ABI
817 int32_t
const l_text{
static_cast<int32_t
>(os.
str().size())};
819 int32_t
const l_text{
static_cast<int32_t
>(os.view().size())};
823#if SEQAN3_WORKAROUND_GCC_NO_CXX11_ABI
824 auto header_view = os.
str();
826 auto header_view = os.view();
830 assert(header.ref_ids().size() < (1ull << 32));
831 int32_t
const n_ref{
static_cast<int32_t
>(header.ref_ids().size())};
834 for (int32_t ridx = 0; ridx < n_ref; ++ridx)
836 assert(header.ref_ids()[ridx].size() + 1 < (1ull << 32));
837 int32_t
const l_name{
static_cast<int32_t
>(header.ref_ids()[ridx].size()) + 1};
843 std::ranges::copy_n(
reinterpret_cast<char *
>(&get<0>(header.ref_id_info[ridx])), 4, stream_it);
866template <
typename value_type>
869 value_type
const & SEQAN3_DOXYGEN_ONLY(value))
871 auto it = str.
begin();
874 int32_t
const vector_size = [&]()
882 int32_t bytes_left{vector_size};
885 tmp_vector.
reserve(vector_size);
889 while (bytes_left > 0)
891 if constexpr (std::integral<value_type>)
893 else if constexpr (std::same_as<value_type, float>)
896 static_assert(std::is_same_v<value_type, void>,
"format_bam::read_sam_dict_vector: unsupported value_type");
903 variant = std::move(tmp_vector);
931 auto it = tag_str.
begin();
934 auto parse_integer_into_target = [&]<std::integral int_t>(uint16_t
const tag, int_t)
938 target[tag] =
static_cast<int32_t
>(tmp);
943 auto parse_array_into_target = [&]<
arithmetic array_value_t>(uint16_t
const tag, array_value_t)
946 it +=
sizeof(int32_t) +
sizeof(array_value_t) * count;
950 auto parse_tag = [&]()
952 uint16_t tag =
static_cast<uint16_t
>(*it) << 8;
954 tag |=
static_cast<uint16_t
>(*it);
959 while (it != tag_str.
end())
961 uint16_t
const tag = parse_tag();
963 char const type_id{*it};
977 parse_integer_into_target(tag, int8_t{});
982 parse_integer_into_target(tag, uint8_t{});
987 parse_integer_into_target(tag, int16_t{});
992 parse_integer_into_target(tag, uint16_t{});
997 parse_integer_into_target(tag, int32_t{});
1002 parse_integer_into_target(tag, uint32_t{});
1010 it +=
sizeof(float);
1015 std::string const v{
static_cast<char const *
>(it)};
1017 target[tag] = std::move(v);
1026 uint8_t dummy_byte{};
1028 if (str.
size() % 2 != 0)
1029 throw format_error{
"[CORRUPTED BAM FILE] Hexadecimal tag must have even number of digits."};
1033 for (
auto hex_begin = str.
begin(), hex_end = str.
begin() + 2; hex_begin != str.
end();
1034 hex_begin += 2, hex_end += 2)
1038 if (res.ec == std::errc::invalid_argument)
1040 +
std::string(hex_begin, hex_end) +
"' could not be cast into type uint8_t."};
1042 if (res.ec == std::errc::result_out_of_range)
1044 +
"' into type uint8_t would cause an overflow."};
1049 target[tag] = std::move(tmp_vector);
1051 it += str.
size() + 1;
1057 char array_value_type_id = *it;
1060 switch (array_value_type_id)
1063 parse_array_into_target(tag, int8_t{});
1066 parse_array_into_target(tag, uint8_t{});
1069 parse_array_into_target(tag, int16_t{});
1072 parse_array_into_target(tag, uint16_t{});
1075 parse_array_into_target(tag, int32_t{});
1078 parse_array_into_target(tag, uint32_t{});
1081 parse_array_into_target(tag,
float{});
1085 "must be one of [cCsSiIf] but '",
1086 array_value_type_id,
1093 "SAM tag must be one of [A,i,Z,H,B,f] but '",
1109 cigar_operation_mapping{
'M',
'I',
'D',
'N',
'S',
'H',
'P',
'=',
'X',
'*',
'*',
'*',
'*',
'*',
'*',
'*'};
1111 constexpr uint32_t cigar_operation_mask = 0x0f;
1114 char operation{
'\0'};
1116 uint32_t operation_and_count{};
1118 assert(cigar_str.
size() % 4 == 0);
1120 for (
auto it = cigar_str.
begin(); it != cigar_str.
end(); it +=
sizeof(operation_and_count))
1122 std::memcpy(&operation_and_count, it,
sizeof(operation_and_count));
1123 operation = cigar_operation_mapping[operation_and_count & cigar_operation_mask];
1124 count = operation_and_count >> 4;
1129 return cigar_vector;
1139 auto stream_variant_fn = [&result](
auto && arg)
1144 if constexpr (std::same_as<T, int32_t>)
1147 size_t const absolute_arg = std::abs(arg);
1149 bool const negative = arg < 0;
1150 n = n * n + 2 * negative;
1156 result[result.size() - 1] =
'C';
1157 result.append(
reinterpret_cast<char const *
>(&arg), 1);
1162 result[result.size() - 1] =
'S';
1163 result.append(
reinterpret_cast<char const *
>(&arg), 2);
1168 result[result.size() - 1] =
'c';
1169 int8_t tmp =
static_cast<int8_t
>(arg);
1170 result.append(
reinterpret_cast<char const *
>(&tmp), 1);
1175 result[result.size() - 1] =
's';
1176 int16_t tmp =
static_cast<int16_t
>(arg);
1177 result.append(
reinterpret_cast<char const *
>(&tmp), 2);
1182 result.append(
reinterpret_cast<char const *
>(&arg), 4);
1187 else if constexpr (std::same_as<T, std::string>)
1189 result.append(
reinterpret_cast<char const *
>(arg.data()), arg.size() + 1 );
1191 else if constexpr (!std::ranges::range<T>)
1193 result.append(
reinterpret_cast<char const *
>(&arg),
sizeof(arg));
1197 int32_t sz{
static_cast<int32_t
>(arg.size())};
1198 result.append(
reinterpret_cast<char *
>(&sz), 4);
1199 result.append(
reinterpret_cast<char const *
>(arg.data()),
1200 arg.size() *
sizeof(std::ranges::range_value_t<T>));
1204 for (
auto & [tag, variant] : tag_dict)
1206 result.push_back(
static_cast<char>(tag / 256));
1207 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.