Skip to content

Commit c7f1b67

Browse files
authored
Merge pull request #77 from h-2/rebrand
[misc] shallow records are not copyable
2 parents 49ea919 + 0c1732c commit c7f1b67

27 files changed

+372
-199
lines changed

doc/record_faq.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ performance** but also in some important limitations:
6060
* Shallow records cannot be modified (as easily¹).
6161
* Shallow records cannot be "stored"; they depend on internal caches and buffers of the reader and become invalid
6262
as soon as the next record is read from the file.
63-
63+
* To prevent the user from accidentally copying the record into a scope where it would be invalid, shallow records
64+
are not copyable!
6465

6566
If you need to change a record in-place and/or "store" the record for longer than one iteration of the reader, you need to use *deep records* instead.
6667
You can tell the reader that you want deep records by providing the respective options:

include/bio/io/detail/concept.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#pragma once
1515

1616
#include <concepts>
17+
#include <ranges>
1718

1819
#include <bio/alphabet/concept.hpp>
1920
#include <bio/meta/overloaded.hpp>
@@ -50,7 +51,6 @@ constexpr bool lazy_concept_checker(auto fun)
5051
using ret_t = decltype(meta::overloaded{fallback, fun}(1));
5152
return ret_t::value;
5253
}
53-
5454
//!\}
5555

5656
} // namespace bio::io::detail

include/bio/io/detail/reader_base.hpp

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -137,13 +137,13 @@ class reader_base
137137
* the file is detected as being compressed.
138138
* See the section on compression and decompression (TODO) for more information.
139139
*/
140-
reader_base(std::filesystem::path const & filename, format_type const & fmt, options_t const & opt = options_t{}) :
141-
options{opt}, stream{filename, opt.stream_options}, format{fmt}
140+
reader_base(std::filesystem::path const & filename, format_type const & fmt, options_t opt = options_t{}) :
141+
stream{filename, opt.stream_options}, options{std::move(opt)}, format{fmt}
142142
{}
143143

144144
//!\overload
145-
explicit reader_base(std::filesystem::path const & filename, options_t const & opt = options_t{}) :
146-
options{opt}, stream{filename, opt.stream_options}
145+
explicit reader_base(std::filesystem::path const & filename, options_t opt = options_t{}) :
146+
stream{filename, opt.stream_options}, options{std::move(opt)}
147147
{
148148
// initialise format handler or throw if format is not found
149149
detail::set_format(format, stream.truncated_filename());
@@ -164,17 +164,17 @@ class reader_base
164164
* it is detected as being compressed.
165165
* See the section on compression and decompression (TODO) for more information.
166166
*/
167-
reader_base(std::istream & str, format_type const & fmt, options_t const & opt = options_t{}) :
168-
options{opt}, stream{str, opt.stream_options}, format{fmt}
167+
reader_base(std::istream & str, format_type const & fmt, options_t opt = options_t{}) :
168+
stream{str, opt.stream_options}, options{std::move(opt)}, format{fmt}
169169
{}
170170

171171
//!\overload
172172
template <movable_istream temporary_stream_t>
173173
//!\cond REQ
174174
requires(!std::is_lvalue_reference_v<temporary_stream_t>)
175175
//!\endcond
176-
reader_base(temporary_stream_t && str, format_type const & fmt, options_t const & opt = options_t{}) :
177-
options{opt}, stream{std::move(str), opt.stream_options}, format{fmt}
176+
reader_base(temporary_stream_t && str, format_type const & fmt, options_t opt = options_t{}) :
177+
stream{std::move(str), opt.stream_options}, options{std::move(opt)}, format{fmt}
178178
{}
179179
//!\}
180180

@@ -252,10 +252,10 @@ class reader_base
252252
/*!\name State
253253
* \{
254254
*/
255-
//!\brief The object holding the options.
256-
options_t options;
257255
//!\brief The input stream.
258256
transparent_istream stream;
257+
//!\brief The object holding the options.
258+
options_t options;
259259
//!\brief Buffer for a single record.
260260
record_type record_buffer;
261261
//!\brief Tracks whether the very first record is buffered when calling begin().

include/bio/io/detail/utility.hpp

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include <ranges>
2020
#include <string>
2121

22+
#include <bio/meta/tuple.hpp>
2223
#include <bio/meta/type_list/function.hpp>
2324
#include <bio/meta/type_list/type_list.hpp>
2425
#include <bio/meta/type_traits/template_inspection.hpp>
@@ -32,6 +33,10 @@ namespace bio::io::detail
3233
* \{
3334
*/
3435

36+
//=============================================================================
37+
// move_tracker
38+
//=============================================================================
39+
3540
//!\brief Can be included as a member to infer whether parent is in moved-from state.
3641
//!\ingroup io
3742
struct move_tracker
@@ -55,6 +60,36 @@ struct move_tracker
5560
bool moved_from = false;
5661
};
5762

63+
//=============================================================================
64+
// copy_blocker
65+
//=============================================================================
66+
67+
//!\brief Can be included as a member to prevent the parent from being copyable/movable.
68+
//!\ingroup io
69+
template <bool shallow>
70+
struct copy_blocker
71+
{
72+
/*!\name Constructors, destructor and assignment
73+
* \{
74+
*/
75+
copy_blocker() = default; //!<Defaulted.
76+
copy_blocker(copy_blocker &&) = default; //!<Defaulted.
77+
copy_blocker & operator=(copy_blocker &&) = default; //!<Defaulted.
78+
79+
copy_blocker(copy_blocker const &) requires(!shallow) = default; //!<Conditionally defaulted.
80+
copy_blocker(copy_blocker const &) requires(shallow) = delete; //!<USE DEEP RECORDS IF YOU WANT TO COPY!
81+
copy_blocker & operator=(copy_blocker const &) requires(!shallow) = default; //!<Conditionally defaulted.
82+
copy_blocker & operator=(copy_blocker const &) requires(shallow) = delete; //!<USE DEEP RECORDS IF YOU WANT TO COPY!
83+
//!\}
84+
85+
//!\brief Comparison operator that always compares as equal.
86+
friend auto operator<=>(copy_blocker const & lhs, copy_blocker const & rhs) = default;
87+
};
88+
89+
//=============================================================================
90+
// clear()
91+
//=============================================================================
92+
5893
//!\brief Helper for clearing objects that provide such functionality.
5994
//!\ingroup io
6095
void clear(auto && arg) requires(requires { arg.clear(); })
@@ -68,4 +103,30 @@ void clear(auto && arg)
68103
arg = {};
69104
}
70105

106+
//=============================================================================
107+
// is_shallow_v
108+
//=============================================================================
109+
110+
//!\brief A type that is a an lvalue reference or a copyable view.
111+
template <typename t>
112+
inline constexpr bool is_shallow_v = std::is_lvalue_reference_v<t> ||
113+
(std::ranges::view<t> && std::copy_constructible<t>);
114+
115+
//!\brief A type that is a an lvalue reference or a copyable view. [specialisation for std::tuple]
116+
template <typename... args_t>
117+
inline constexpr bool is_shallow_v<std::tuple<args_t...>> = (is_shallow_v<args_t> || ...);
118+
119+
//!\brief A type that is a an lvalue reference or a copyable view. [specialisation for std::pair]
120+
template <typename... args_t>
121+
inline constexpr bool is_shallow_v<std::pair<args_t...>> = (is_shallow_v<args_t> || ...);
122+
123+
//!\brief A type that is a an lvalue reference or a copyable view. [specialisation for bio::meta::tuple]
124+
template <typename... args_t>
125+
inline constexpr bool is_shallow_v<bio::meta::tuple<args_t...>> = (is_shallow_v<args_t> || ...);
126+
127+
//!\brief A type that is a an lvalue reference or a copyable view. [specialisation for containers]
128+
template <std::ranges::range container_t>
129+
requires(!std::ranges::view<container_t>)
130+
inline constexpr bool is_shallow_v<container_t> = is_shallow_v<std::ranges::range_value_t<container_t>>;
131+
71132
} // namespace bio::io::detail

include/bio/io/seq/reader.hpp

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -139,16 +139,16 @@ class reader : public reader_base<reader<option_args_t...>, reader_options<optio
139139
* the file is detected as being compressed.
140140
* See the section on compression and decompression (TODO) for more information.
141141
*/
142-
reader(std::filesystem::path const & filename,
143-
format_type const & fmt,
144-
reader_options<option_args_t...> const & opt = reader_options<option_args_t...>{}) :
145-
base_t{filename, fmt, opt}
142+
reader(std::filesystem::path const & filename,
143+
format_type const & fmt,
144+
reader_options<option_args_t...> opt = reader_options<option_args_t...>{}) :
145+
base_t{filename, fmt, std::move(opt)}
146146
{}
147147

148148
//!\overload
149-
explicit reader(std::filesystem::path const & filename,
150-
reader_options<option_args_t...> const & opt = reader_options<option_args_t...>{}) :
151-
base_t{filename, opt}
149+
explicit reader(std::filesystem::path const & filename,
150+
reader_options<option_args_t...> opt = reader_options<option_args_t...>{}) :
151+
base_t{filename, std::move(opt)}
152152
{}
153153

154154
/*!\brief Construct from an existing stream and with specified format.
@@ -166,21 +166,21 @@ class reader : public reader_base<reader<option_args_t...>, reader_options<optio
166166
* it is detected as being compressed.
167167
* See the section on compression and decompression (TODO) for more information.
168168
*/
169-
reader(std::istream & str,
170-
format_type const & fmt,
171-
reader_options<option_args_t...> const & opt = reader_options<option_args_t...>{}) :
172-
base_t{str, fmt, opt}
169+
reader(std::istream & str,
170+
format_type const & fmt,
171+
reader_options<option_args_t...> opt = reader_options<option_args_t...>{}) :
172+
base_t{str, fmt, std::move(opt)}
173173
{}
174174

175175
//!\overload
176176
template <movable_istream temporary_stream_t>
177177
//!\cond REQ
178178
requires(!std::is_lvalue_reference_v<temporary_stream_t>)
179179
//!\endcond
180-
reader(temporary_stream_t && str,
181-
format_type const & fmt,
182-
reader_options<option_args_t...> const & opt = reader_options<option_args_t...>{}) :
183-
base_t{std::move(str), fmt, opt}
180+
reader(temporary_stream_t && str,
181+
format_type const & fmt,
182+
reader_options<option_args_t...> opt = reader_options<option_args_t...>{}) :
183+
base_t{std::move(str), fmt, std::move(opt)}
184184
{}
185185
};
186186

include/bio/io/seq/reader_options.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ namespace bio::io::seq
5050
* Furthermore, different container and view types can be chosen. See bio::io::seq::record
5151
* for more details.
5252
*
53+
* If you define the options before creating the reader, it is recommended to `std::move()`
54+
* them into the constructor of the reader, since some members might not be copyable.
55+
*
5356
* ### Example
5457
*
5558
* Options can be easily set via [designated

include/bio/io/seq/record.hpp

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@
1313

1414
#pragma once
1515

16+
#include <ranges>
1617
#include <string>
18+
#include <type_traits>
1719
#include <vector>
1820

1921
#include <bio/alphabet/aminoacid/aa27.hpp>
@@ -29,6 +31,7 @@
2931
#include <bio/io/detail/misc.hpp>
3032
#include <bio/io/detail/range.hpp>
3133
#include <bio/io/detail/tuple_record.hpp>
34+
#include <bio/io/detail/utility.hpp>
3235
#include <bio/io/format/fasta.hpp>
3336
#include <bio/io/format/fastq.hpp>
3437
#include <bio/io/misc.hpp>
@@ -163,8 +166,59 @@ struct record
163166
* The default and all pre-defined aliases satisfy the requirements for reading and writing.
164167
*/
165168
qual_t qual{};
169+
170+
//!\brief Defaulted comparison operators.
171+
friend auto operator<=>(record const & lhs, record const & rhs) = default;
172+
173+
//!\privatesection
174+
//!\brief Type of the copy_blocker.
175+
using _copy_blocker_t =
176+
io::detail::copy_blocker<io::detail::is_shallow_v<id_t> || io::detail::is_shallow_v<seq_t> ||
177+
io::detail::is_shallow_v<qual_t>>;
178+
//!\brief Empty member that prevents this class from being copyable if it contains views or references.
179+
[[no_unique_address]] _copy_blocker_t _shallow_record_not_copyable_use_deep_record_instead{};
180+
181+
//!\brief Enable structured bindings for this class that ignore the copy_blocker.
182+
template <size_t i>
183+
requires(i < 3)
184+
friend decltype(auto) get(meta::decays_to<record> auto && r)
185+
{
186+
auto fwd_like_r = []<typename t>(t & mem) -> decltype(auto)
187+
{ return static_cast<std::conditional_t<std::is_lvalue_reference_v<decltype(r)>, t &, t &&>>(mem); };
188+
189+
if constexpr (i == 0)
190+
return fwd_like_r(r.id);
191+
else if constexpr (i == 1)
192+
return fwd_like_r(r.seq);
193+
else
194+
return fwd_like_r(r.qual);
195+
}
166196
};
167197

198+
} // namespace bio::io::seq
199+
200+
//!\cond
201+
namespace std
202+
{
203+
204+
template <typename... args>
205+
struct tuple_size<bio::io::seq::record<args...>> : public std::integral_constant<size_t, 3>
206+
{};
207+
208+
template <size_t i, typename... args>
209+
struct tuple_element<i, bio::io::seq::record<args...>> : tuple_element<i, std::tuple<args...>>
210+
{};
211+
212+
template <size_t i, typename... args>
213+
struct tuple_element<i, bio::io::seq::record<args...> const> : tuple_element<i, std::tuple<args...> const>
214+
{};
215+
216+
} // namespace std
217+
//!\endcond
218+
219+
namespace bio::io::seq
220+
{
221+
168222
//-----------------------------------------------------------------------------
169223
// tie_record()
170224
//-----------------------------------------------------------------------------

include/bio/io/txt/misc.hpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,16 @@ namespace bio::io::txt
3636
*/
3737
struct record
3838
{
39+
/*!\name Constructors, destructor and assignment
40+
* \{
41+
*/
42+
record() = default; //!< Defaulted.
43+
record(record &&) = default; //!< Defaulted.
44+
record(record const &) = delete; //!< Deleted to prevent dangling copies.
45+
record & operator=(record &&) = default; //!< Defaulted.
46+
record & operator=(record const &) = delete; //!< Deleted to prevent dangling copies.
47+
//!\}
48+
3949
//!\brief The entire line (exluding EOL characters but including delimiters).
4050
std::string_view line;
4151
//!\brief A range of the individual fields (without delimiters or EOL characters).

0 commit comments

Comments
 (0)