【发布时间】:2021-01-18 21:02:57
【问题描述】:
我试图为c++20 的ranges 的扩展实现通用归约操作,它将任何range 的元素收集到给定容器中。为此,我首先创建了一个虚拟类型来提取template template 参数,并提供operator| 用于将range 与它结合起来:
template <template <typename> typename T>
struct to_fn { };
template <template <typename> typename T>
inline constexpr detail::functors::to_fn<T> to;
template <template <typename> typename T>
auto operator|(std::ranges::range auto&& rng, detail::functors::to_fn<T>) {
return T(std::ranges::begin(rng), std::ranges::end(rng));
}
测试如下:
int main() {
using namespace std::ranges;
std::vector<int> vec = {1, 2, 3, 4, 5};
auto set = vec | to<std::set>;
static_assert(std::same_as<decltype(set), std::set<int>>);
assert(equal(vec, set));
}
代码完成执行没有问题。
但是,当与std::ranges::istream_view 一起使用时,代码编译失败:
int main() {
using namespace std::ranges;
std::ifstream input_file("input.txt");
auto vec = istream_view<int>(input_file) | to<std::vector>;
}
这个编译失败出现一大堆错误,在我看来,其中最重要的一个是:
note: deduced conflicting types for parameter '_InputIterator' ('std::ranges::basic_istream_view<int, char, std::char_traits<char> >::_Iterator' and 'std::default_sentinel_t') 122 | return T(std::ranges::begin(rng), std::ranges::end(rng)); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
这对我来说很有意义。容器要求用于通过构造函数构造它们的迭代器使用其中的两个是相同的类型。
但这很好 - 这就是 std::ranges::views::common_view 的创建目的。所以我尝试将operator|修改为:
template <template <typename> typename T>
auto operator|(std::ranges::range auto&& rng, detail::functors::to_fn<T>) {
auto common = rng | std::ranges::views::common;
return T(std::ranges::begin(common), std::ranges::end(common));
}
再次,未能编译并出现较小的错误墙,其中我认为这是最相关的:
note: the expression 'is_constructible_v<_Tp, _Args ...> [with _Tp = std::ranges::basic_istream_view<int, char, std::char_traits<char> >::_Iterator<int, char, std::char_traits<char> >; _Args = {std::ranges::basic_istream_view<int, char, std::char_traits<char> >::_Iterator<int, char, std::char_traits<char> >&}]' evaluated to 'false' 139 | = destructible<_Tp> && is_constructible_v<_Tp, _Args...>; | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
我不太明白这个错误的含义,但我想这意味着istream_view 不能被复制构造。对我来说有点道理。
但我真的希望我能拥有这个通用的to“函子”。当我们推断我们正在处理 输入范围1.
所以我尝试了这个:
template <template <typename> typename T>
auto operator|(std::ranges::range auto&& rng, detail::functors::to_fn<T>) {
using namespace std::ranges;
using range_t = decltype(rng);
const bool input_range = std::is_same_v<
iterator_t<range_t>::iterator_category,
std::input_iterator_tag>;
if constexpr(input_range) {
auto container = T<range_value_t<range_t>>();
for (auto&& element : rng) {
container.generic_add(element); // ???
}
return container;
} else {
auto common = rng | views::common;
return T(begin(common), end(common));
}
}
然后告诉我,除其他外:
error: 'iterator_category' is not a member of 'std::ranges::iterator_t<std::ranges::basic_istream_view<int, char, std::char_traits<char> >&&>' 125 | iterator_t<range_t>::iterator_category, | ^~~~~~~~~~~~~~~~~
这不是唯一的问题。通常将元素添加到任何容器中也存在问题。据我所知,采用range 的构造函数是向容器添加元素的唯一通用方式和好 方式。
我觉得必须有一种正确且更简单的方法来做我想做的事情。如果to 也适用于非模板,则可以加分,即我不仅可以使用to<std::vector>,还可以使用to<std::string>。在第一种情况下,它将推断元素并创建所需的std::vector 实例化,但在第二种情况下,它将获取所有元素并用这些元素初始化std::string。我怎样才能做到这一点?
1 这假设实际问题在于我们使用的是输入范围。我不确定是不是这样。如果有人能指出我推理中可能存在的错误,我会很高兴。
【问题讨论】:
标签: c++20 c++ containers c++20 std-ranges istream-view