Reputation: 1501
I'm new to concepts and ranges/views.
I'm trying to write the initialization of a class by passing a sequence of values defined by an iterator or by a range/view.
I'm able to check that the function arguments are iterators or ranges. But I can't check that the values returned by the iterators are a specific type.
For example (C++23):
#include <print>
#include <vector>
struct Data {
int i;
std::string l;
};
struct DataContainer {
// iterator version
// QUESTION: how can I check that I handles "Data"?
template<std::input_iterator I, std::sentinel_for<I> S>
void add(I start, S end) {
for (auto data = start; data != end; ++data) {
_data.push_back(*data);
}
}
// range/views version
// QUESTION: how can I check that R handles "Data"?
template<std::ranges::input_range R>
void add(R &&r) {
add(std::begin(r), std::end(r));
}
void dump() const {
for (const auto& d: _data){
std::print("[{},'{}'], ", d.i, d.l);
}
std::println();
}
std::vector<Data> _data;
};
int main()
{
std::vector<Data> init{{1, "one"}, {2, "two"}, {3, "three"}};
{
DataContainer dc;
dc.add(init.begin(), init.end());
dc.dump();
}
{
DataContainer dc;
dc.add(init);
dc.dump();
}
return 0;
}
How can I check that *start
returns a Data
?
Upvotes: 5
Views: 143
Reputation: 117832
In both cases you could add a constraint:
template <std::input_iterator I, std::sentinel_for<I> S>
requires std::convertible_to<std::iter_value_t<I>, Data> // constraint added
void add(I start, S end) {
// ...
}
template <std::ranges::input_range R>
requires std::convertible_to<std::ranges::range_value_t<R>, Data> // constraint added
void add(R&& r) {
// ...
}
The concept
convertible_to<From, To>
specifies that an expression of the same type and value category as those ofstd::declval<From>()
can be implicitly and explicitly converted to the typeTo
, and the two forms of conversion produce equal results.
- Computes the value type of
T
.
- If
std::iterator_traits<std::remove_cvref_t<T>>
is not specialized, thenstd::iter_value_t<T>
isstd::indirectly_readable_traits<std::remove_cvref_t<T>>::value_type
.- Otherwise, it is
std::iterator_traits<std::remove_cvref_t<T>>::value_type
.
Used to obtain the value type of the iterator type of range type
R
.
A more relaxed version could use std::constructible_from
instead of std::convertible_to
.
Upvotes: 6
Reputation: 342
Barry gave a great comprehensive answer!
Just as a small addition - if you use standard algorithms (in this case std::ranges::copy
), then the code can be made more concise, and additional requires
do not need to be written - it is "sewn" into the algorithm itself:
template<std::ranges::input_range R>
void add1(R&& r)
{
std::ranges::copy(r, std::back_inserter(_data));
}
Upvotes: 0
Reputation: 303636
For this one:
// iterator version
// QUESTION: how can I check that I handles "Data"?
template<std::input_iterator I, std::sentinel_for<I> S>
void add(I start, S end) {
for (auto data = start; data != end; ++data) {
_data.push_back(*data);
}
}
The type of *data
is iter_reference_t<I>
. This is called the iterator's reference type, which is a bit misleading because it doesn't actually have to be any kind of reference. Typically, algorithms should be constrained based on the reference type - that's what you actually interact with. So this becomes:
template <std::input_iterator I, std::sentinel_for<I> S>
requires std::convertible_to<std::iter_reference_t<I>, Data>
void add(I, S);
You really should only be using the value type of an iterator if you actually need to produce values.
For this one:
// range/views version
// QUESTION: how can I check that R handles "Data"?
template<std::ranges::input_range R>
void add(R &&r) {
The idea is the same, it's just spelled std::ranges::range_reference_t<R>
. This is defined to be std::iter_reference_t<std::ranges::iterator_t<R>>
(i.e. the reference type of the range is the reference type of the range's iterator type).
template <std::ranges::input_range R>
requires std::convertible_to<std::ranges::range_reference_t<R>, Data>
void add(R &&r) {
add(std::ranges::begin(r), std::ranges::end(r));
}
Note that this needs to use std::ranges::{begin,end}
, not std::{begin,end}
. The latter is incorrect for some range types - those that define begin
and end
as non-member functions that are found by ADL, but aren't in std
.
Upvotes: 11