我对概念和范围/观点还很陌生。
我正在尝试通过传递由迭代器或范围/视图定义的值序列来编写类的初始化。
我可以检查函数参数是迭代器还是范围。但我无法检查迭代器返回的值是否是特定类型。
例如(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;
}
我如何检查是否*start
返回Data
?
对于这个:
的类型
*data
是iter_reference_t<I>
。这被称为迭代器的引用类型,这有点误导,因为它实际上不必是任何类型的引用。通常,算法应该基于引用类型进行约束 - 这就是您实际与之交互的内容。因此,这变成:如果您确实需要生成值,那么您真的应该只使用迭代器的值类型。
对于这个:
思路是一样的,只是拼写为
std::ranges::range_reference_t<R>
。这定义为std::iter_reference_t<std::ranges::iterator_t<R>>
(即范围的引用类型是范围的迭代器类型的引用类型)。请注意,这需要使用
std::ranges::{begin,end}
,而不是std::{begin,end}
。后者对于某些范围类型是不正确的 - 那些将begin
和定义end
为 ADL 找到但不在 中的非成员函数std
。在这两种情况下,您都可以添加约束:
std::convertible_to
:std::iter_value_t
std::ranges::range_value_t
:更为宽松的版本可以使用
std::constructible_from
来代替std::convertible_to
。巴里给出了非常全面的答案!
只是作为一个小小的补充——如果你使用标准算法(在本例中
std::ranges::copy
),那么代码可以变得更简洁,并且requires
不需要编写额外的代码——它被“缝合”到算法本身中: