如何手动为非随机访问range的transform+join视图指定大小?
为bit视图手动实现sized_range的解决方案
一、为什么transform+join后的视图不满足sized_range?
标准库的join_view仅在底层range是sized_range,且每个子range都是sized_range,同时底层range是forward_range及以上时,才会自动满足sized_range。虽然你的transform操作把每个std::byte转成了固定8个元素的sized子range,但如果原range只是input_range而非forward_range,join_view无法保证安全计算总大小,因此不会自动标记为sized_range。
二、如何为你的bit视图手动指定大小?
可以自定义一个包装视图,组合transform+join的逻辑,并为其手动实现size()成员函数,同时确保满足sized_range概念:
实现示例
#include <ranges> #include <cstddef> #include <stdbyte> template<std::ranges::input_range R> requires std::same_as<std::ranges::range_value_t<R>, std::byte> class bit_view : public std::ranges::view_interface<bit_view<R>> { private: R base_; // 内部生成byte转bit的transform+join视图 auto inner_view() const { return std::ranges::transform_view(base_, [](std::byte b) { return std::views::iota(0, 8) | std::views::transform([b](int i) { return static_cast<bool>((b >> i) & std::byte{1}); }); }) | std::views::join; } public: explicit bit_view(R base) : base_(std::move(base)) {} auto begin() const { return inner_view().begin(); } auto end() const { return inner_view().end(); } // 仅当原range是sized_range时,提供size() std::size_t size() const requires std::ranges::sized_range<R> { return std::ranges::size(base_) * 8; } }; // 特化sized_range概念,让编译器识别该视图满足sized_range template<std::ranges::sized_range R> requires std::same_as<std::ranges::range_value_t<R>, std::byte> inline constexpr bool std::ranges::sized_range<bit_view<R>> = true; // 非随机访问版本的to_bits template<std::ranges::input_range R> requires (!std::ranges::random_access_range<R> && std::same_as<std::ranges::range_value_t<R>, std::byte>) auto to_bits(R&& r) { return bit_view(std::forward<R>(r)); } // 你的随机访问版本to_bits保持不变 template<std::ranges::random_access_range R> requires std::same_as<std::ranges::range_value_t<R>, std::byte> auto to_bits(R&& r) { // 这里实现你的随机访问bit视图逻辑 }
关键要点
- 继承
std::ranges::view_interface可以自动获得empty()、operator[](如果满足条件)等默认实现,减少重复代码。 - 仅在原range是sized_range时提供
size(),直接返回原大小 * 8(每个byte对应8个bit),保证大小计算准确。 - 手动特化
std::ranges::sized_range,确保编译器能识别该视图符合sized_range概念,让依赖该概念的算法正常工作。
三、实现手动指定大小的视图是否可行?
完全可行。你可以自定义一个通用的sized包装视图,接收任意无size的input_range和指定的大小值,实现size()返回该值,并满足sized_range概念:
实现示例
#include <ranges> #include <cstddef> #include <stdexcept> template<std::ranges::input_range R> class sized_wrapper : public std::ranges::view_interface<sized_wrapper<R>> { private: R base_; std::size_t size_; public: sized_wrapper(R base, std::size_t size) : base_(std::move(base)), size_(size) { // 可选:如果底层是forward_range,可以验证大小匹配(有性能开销) if constexpr (std::ranges::forward_range<R>) { const auto actual_size = std::ranges::distance(base_); if (actual_size != size_) { throw std::invalid_argument("指定的大小与实际range元素数量不匹配"); } } } auto begin() const { return std::ranges::begin(base_); } auto end() const { return std::ranges::end(base_); } std::size_t size() const noexcept { return size_; } }; // 特化sized_range概念 template<std::ranges::input_range R> inline constexpr bool std::ranges::sized_range<sized_wrapper<R>> = true; // 使用示例 auto make_unsized_range() -> std::ranges::input_range auto { // 返回一个无size的input_range,比如自定义的生成器 struct generator { int current = 0; bool operator==(std::default_sentinel_t) const { return current >= 16; } int operator*() const { return current; } generator& operator++() { ++current; return *this; } }; return generator{}; } int main() { auto unsized = make_unsized_range(); auto sized_view = sized_wrapper(unsized, 16); static_assert(std::ranges::sized_range<decltype(sized_view)>); // 可以安全使用size() assert(sized_view.size() == 16); }
注意事项
- 手动指定的大小必须与实际range的元素数量严格一致,否则在调用
std::ranges::distance、std::views::take等依赖size的操作时会导致未定义行为。 - 对于forward_range及以上的底层视图,可以在构造时验证大小匹配,但会带来遍历整个range的性能开销,根据场景选择是否启用。
内容的提问来源于stack exchange,提问作者Георгий Гуминов
相关产品推荐
相关产品推荐

