ROOT
ROOT
文章目录
  1. 前言
  2. 日历程序问题分解
  3. 使用 Ranges 标准库实现
  4. 遇到的一些问题
  5. 更多实现细节

使用 C++20 Ranges 标准库实现日历程序

前言

花了我 3 个晚上才搞定,结论是目前 C++20 的 Ranges 标准库 对于实现复杂的程序还不够用,提供的 views 适配器组合子也仅仅局限于简单的 filter/transform 等,并且未提供标准的方式让用户去定义组合子(不过这个问题目前 C++23 已经有提案 P2387R0 在做了)。

通过自定义 views 组合子并完成一个相对复杂的程序,对于理解 Ranges 库的机制与原理是相当有帮助的。

完整可编译可运行的日历程序请见:https://godbolt.org/z/overc6q51,如果关注后续的代码更新,请见Calendar.cpp

效果如下(2021 年)

      January               February               March
Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa
                1  2      1  2  3  4  5  6      1  2  3  4  5  6
 3  4  5  6  7  8  9   7  8  9 10 11 12 13   7  8  9 10 11 12 13
10 11 12 13 14 15 16  14 15 16 17 18 19 20  14 15 16 17 18 19 20
17 18 19 20 21 22 23  21 22 23 24 25 26 27  21 22 23 24 25 26 27
24 25 26 27 28 29 30  28                    28 29 30 31
31
       April                  May                   June
Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa
             1  2  3                     1         1  2  3  4  5
 4  5  6  7  8  9 10   2  3  4  5  6  7  8   6  7  8  9 10 11 12
11 12 13 14 15 16 17   9 10 11 12 13 14 15  13 14 15 16 17 18 19
18 19 20 21 22 23 24  16 17 18 19 20 21 22  20 21 22 23 24 25 26
25 26 27 28 29 30     23 24 25 26 27 28 29  27 28 29 30
                      30 31
        July                 August              September
Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa
             1  2  3   1  2  3  4  5  6  7            1  2  3  4
 4  5  6  7  8  9 10   8  9 10 11 12 13 14   5  6  7  8  9 10 11
11 12 13 14 15 16 17  15 16 17 18 19 20 21  12 13 14 15 16 17 18
18 19 20 21 22 23 24  22 23 24 25 26 27 28  19 20 21 22 23 24 25
25 26 27 28 29 30 31  29 30 31              26 27 28 29 30

      October               November              December
Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa  Su Mo Tu We Th Fr Sa
                1  2      1  2  3  4  5  6            1  2  3  4
 3  4  5  6  7  8  9   7  8  9 10 11 12 13   5  6  7  8  9 10 11
10 11 12 13 14 15 16  14 15 16 17 18 19 20  12 13 14 15 16 17 18
17 18 19 20 21 22 23  21 22 23 24 25 26 27  19 20 21 22 23 24 25
24 25 26 27 28 29 30  28 29 30              26 27 28 29 30 31
31

日历程序问题分解

最终输出的结果是字符串,问题域如下:

  • 以年为单位,一年 355/356 天,如何将每天按月、按周分组?(提示:group_by)
  • 每个月每行输出的是一周 7 天,每天占宽 3 个字节,加一个填充位,总共 3 * 7 + 1 = 22 字节
  • 每个月显示一行月份标题,一行星期标题,最多 6 周(6 行),不足 6 行的补齐空行到 6 行,因此一个月的字符矩阵为(6+2) x 22,总大小为 12 x 8 x 22
  • 如何一行显示 N 个月的字符串信息?(提示:chunk 分块)得到矩阵为(12 / N) x N x 8 x 22
  • 假设一行显示 3 个月的字符串信息,那么矩阵大小是 4 x 3 x 8 x 22,如何将一行 3 个月的信息放到一起?(提示:矩阵转置)
  • 上一步转置之后矩阵维度大小为 4 x 8 x 3 x 22,如何降维处理?(提示:join),得到 32 x 3 x 22
  • 如何对低维的 3 x 22 进行降维处理(字符串拼接),得到一行长度为 66 的字符串?(提示:transform/join)
  • 如何输出最终的字符矩阵 32 x 66?(提示:std::cout)

如果让你编写这个程序,如何编写?(似乎作为一个面试题不错)是不是涉及一大堆循环、分支代码?涉及一大堆迭代变量(状态)?这就是常规的命令式(面向过程)编程风格。

我正好找到了一个使用命令式编程风格的日历实现:https://github.com/karelzak/util-linux/blob/master/misc-utils/cal.c,里面的最大分支循环嵌套深度居然达到了 5 层(cal_vert_output_months 函数),当一个人每面对一层嵌套,理解难度成倍增长。分支与循环引入的迭代状态与过深的嵌套层数,漫无边际的细节与面条式代码,不仅容易出错且非常难读。

而 Ranges 标准库使用函数式编程风格,它的最大优势是无状态编程,如果一个程序的状态越少,越容易推理,正确性越容易证明(bug 越少)。另一个优势在于行为的灵活组合能力,每一步操作(组合子)都是原子与抽象的可复用可组合的动作,对每一层的 Range 处理不会引入额外的嵌套,利用这些原子动作可以组合出任意强大的程序。

使用 Ranges 标准库实现

如下是我使用 C++20 标准重写的代码,欣赏一下。这份代码参考了 range-v3 库(Ranges 标准库的基础)中的例子,具体思想可参考原作者于 CppCon 2015 年的演讲https://www.youtube.com/watch?v=mFUXNMfaciE

int main(int argc, char** argv) {
    auto formatedCalendar
        = datesBetween(2021, 2022) // range<Date>: 356/366
        | by_month()               // range<range<Date>>: 12 x month
        | layout_months()          // range<range<std::string>>: 12 x 8 x 22
        | chunk(3)                 // range<range<range<std::string>>>: 4 x 3 x 8 x 22
        | transpose_months()       // range<range<range<std::string>>>: 4 x 8 x 3 x 22
        | views::join              // range<range<std::string>>: 32 x 3 x 22
        | join_months()            // range<std::string>: 32 x 66
        ;

    ranges::copy(formatedCalendar,
                 std::ostream_iterator<std::string_view>(std::cout, "\n"));
}

auto datesBetween(unsigned short start, unsigned short stop) {
    return views::iota(Date{start, 1, 1}, Date{stop, 1, 1});
}

auto by_month() {
    return group_by([](Date a, Date b) { return a.month() == b.month(); });
}

auto by_week() {
    return group_by([](Date a, Date b) { return (++a).week_number() == (++b).week_number(); });
}

// TODO: c++20 std::format
std::string month_title(const Date& d) {
    std::string title(22, '');
    std::string_view name = d.monthName();
    ranges::copy(name, ranges::begin(title) + (22 - name.length()) / 2);
    return title;
}

// TODO: c++20 std::format
std::string format_day(const Date& d) {
    char res[4];
    sprintf(res, "%3d", (int)d.day());
    return res;
}

// in: range<range<Date>>
// out: range<std::string>
auto format_weeks() {
    return views::transform([](/*range<Date>*/auto&& week) {
        std::string weeks((*ranges::begin(week)).dayOfWeek() * 3, '');
        weeks += (week | views::transform(format_day) | join);
        while (weeks.length() < 22) weeks.push_back('');
        return weeks;
    });
}

// in: range<range<Date>>
// out: range<range<std::string>>
auto layout_months() {
    return views::transform([](/*range<Date>*/ auto&& month) {
        auto week_count = ranges::distance(month | by_week());
        return concat(
            views::single(month_title(*ranges::begin(month))),
            views::single(std::string("Su Mo Tu We Th Fr Sa")),
            month | by_week() | format_weeks(),
            repeat_n(std::string(22, ''), 6 - week_count)
        );
    });
}

// In:  range<range<range<string>>>
// Out: range<range<range<string>>>, transposing months.
auto transpose_months() {
    return views::transform([](/*range<range<string>>*/ auto&& rng) {
        return rng | transpose;
    });
}

// In:  range<range<string>>
// Out: range<string>, joining the strings of the inner ranges
auto join_months()
{
    return views::transform(
        [](/*range<string>*/ auto rng) { return join(rng); });
}

正因为 Ranges 标准库的实现使用了元编程技术,性能并不比命令式的编程风格差。参考最终的汇编代码生成部分,只有少数几行代码涉及生成,其他代码都被优化掉了(开启 O2 优化,高亮部分为参与代码生成的部分)。

只有少数几行代码涉及生成

使用系统自带的日历程序输出 2000 年到 12000 年之间的日历信息,总耗时 0m3.555s,而使用我这个 Ranges 程序,总耗时 0m2.779s。

$ time cal 2000 -A 120000
real	0m3.555s
user	0m0.658s
sys	0m1.799s
$ time ./a.out
real	0m2.779s
user	0m1.169s
sys	0m0.617s

看输出可以推测出系统自带的日历程序可能是边计算边输出,而 Ranges 版本的日历程序是全部计算出结果后一次性输出。

另一个优势在于 Range 的实现是延迟计算的,并且多次组合的背后可能仅仅迭代一次 Range。

生成器(generator)作为协程(coroutine)的一种特殊场景,也能够和 Ranges 进行组合。

最终的二进制大小仅仅为 52KB,去除符号表仅仅 36KB(操作系统环境 Debian bullseye),系统自带的日历程序大小为 40KB。

遇到的一些问题

遇到比较麻烦的问题是编译错误信息,组合后的类型相当长,一旦出错,告警提示的类型将淹没你所需要关注的点。这可能是静态类型语言的弱势。

例如表达式 iota(1, 100) | views::transform([](auto x){ return x * 2; }); 的类型为std::ranges::transform_view<std::ranges::iota_view<int, int>, main(int, char**)::<lambda(auto)> >,一旦报错显示的类型相当长。

如下是我在编译过程中遇到类型爆炸的信息,简洁的背后是有一定代价的。

在编译过程中遇到类型爆炸的信息

更多实现细节

日期 Date 类的实现比较 tricky,我的实现细节如下。回头考虑使用 C++20 标准库 std::chrono::year_month_day 来做。

// Date
struct Date {
    using difference_type = std::ptrdiff_t;
    Date() = default;
    Date(uint16_t year, uint16_t month, uint16_t day):
        days(dayNumber(year, month, day)) { }

    friend bool operator==(const Date&, const Date&) = default;
    Date& operator++()            { ++days; return *this;}
    Date operator++(int)          { Date tmp(*this); ++*this; return tmp; }
    uint16_t day() const          { return fromDayNumber().day;           }
    uint16_t month() const        { return fromDayNumber().month;         }
    const char* monthName() const { return MONTH_NAME[month()];           }
    uint16_t year() const         { return fromDayNumber().year;          }
    uint16_t week_number() const {
        auto beginDays = dayNumber(year(), 1, 1);
        unsigned long day = (beginDays + 3) % 7;
        unsigned long week = (days + day - beginDays + 4)/7;

        if ((week>= 1) && (week <= 52)) { return static_cast<int>(week); }

        if (week == 53) {
            if((day==6) ||(day == 5 && isLeapYear(year()))) {
                return week; //under these circumstances week == 53.
            } else {
                return 1; //monday - wednesday is in week 1 of next year
            }
        }
        //if the week is not in current year recalculate using the previous year as the beginning year
        else if (week == 0) {
            beginDays = dayNumber(year()-1,1,1);
            day = (beginDays + 3) % 7;
            week = (days + day - beginDays + 4)/7;
            return week;
        }
        return week;  //not reachable -- well except if day == 5 and is_leap_year != true
    }
    uint16_t dayOfWeek() const {
        uint16_t a = static_cast<uint16_t>((14-month())/12);
        uint16_t y = static_cast<uint16_t>(year() - a);
        uint16_t m = static_cast<uint16_t>(month() + 12*a - 2);
        uint16_t d = static_cast<uint16_t>((day() + y + (y/4) - (y/100) + (y/400) + (31*m)/12) % 7);
        return d;
    }

private:
    constexpr bool isLeapYear(uint16_t year) const {
        return (!(year % 4))  && ((year % 100) || (!(year % 400)));
    }
    struct ymd {
        uint16_t year;
        uint16_t month;
        uint16_t day;
    };
    constexpr long dayNumber(uint16_t year, uint16_t month, uint16_t day) const {
        uint16_t a = static_cast<uint16_t>((14-month)/12);
        uint16_t y = static_cast<uint16_t>(year + 4800 - a);
        uint16_t m = static_cast<uint16_t>(month + 12*a - 3);
        return day + ((153*m + 2)/5) + 365*y + (y/4) - (y/100) + (y/400) - 32045;
    }
    constexpr ymd fromDayNumber() const {
        uint32_t a = days + 32044;
        uint32_t b = (4*a + 3)/146097;
        uint32_t c = a-((146097*b)/4);
        uint32_t d = (4*c + 3)/1461;
        uint32_t e = c - (1461*d)/4;
        uint32_t m = (5*e + 2)/153;
        uint16_t day = static_cast<uint16_t>(e - ((153*m + 2)/5) + 1);
        uint16_t month = static_cast<uint16_t>(m + 3 - 12 * (m/10));
        uint16_t year = static_cast<uint16_t>(100*b + d - 4800 + (m/10));
        return {year,month,day};
    }

    static constexpr const char* MONTH_NAME[] = {
        "","January","February","March",
        "April", "May", "June", "July",
        "August", "September", "October",
        "November", "December",
    };
    long days;
};

由于 Ranges 标准库支持的 views 适配器组合子比较少,最后附上自己实现的几个组合子,目前没有标准方式去定义组合子,所以实现比较 tricky。但对于一个 views 有标准的方式去定义,通过奇异递归模板模式继承 view_interface 接口即可,感兴趣的可以细看。

///////////////////////////////////////////////////////////////////////////////
// group_by_view
template<ranges::viewable_range Rng, typename Pred>
struct group_by_view: ranges::view_interface<group_by_view<Rng, Pred>> {
    group_by_view() = default;
    group_by_view(Rng r, Pred p): r(std::move(r)), p(std::move(p)) {}

private:
    struct GroupIterator {
        using difference_type = std::ptrdiff_t;
        using value_type = ranges::subrange<ranges::iterator_t<Rng>>;

        GroupIterator& operator++() {
            cur = next_cur;
            if (cur != last) {
                next_cur = ranges::find_if_not(ranges::next(cur), last, [&](auto&& elem)
                                                                        { return p(*cur, elem); });
            }
            return *this;
        }
        GroupIterator operator++(int) {
            GroupIterator tmp(*this);
            ++*this;
            return tmp;
        }
        value_type operator*() const { return {cur, next_cur}; }

        friend bool operator==(const GroupIterator& iter, std::default_sentinel_t)
        { return iter.cur == iter.last; }

        // C++20 defaulted comparison operators
        friend bool operator==(const GroupIterator& lhs, const GroupIterator& rhs) = default;

        Pred p;
        ranges::iterator_t<Rng> cur{};
        ranges::iterator_t<Rng> next_cur{};
        ranges::sentinel_t<Rng> last{};
    };
public:

    GroupIterator begin() {
        auto beg = ranges::begin(r);
        auto end = ranges::end(r);
        return {p, beg,
            ranges::find_if_not(ranges::next(beg), end,
                                [&](auto&& elem) { return p(*beg, elem); }),
                end};
    }

    std::default_sentinel_t end() { return {}; }

    Rng r;
    Pred p;
};

#if __GNUC__ > 10
struct _Group_by: views::__adaptor::_RangeAdaptor<_Group_by> {
    template<ranges::viewable_range Rng, typename Pred>
    constexpr auto operator()(Rng&& r, Pred&& p) const {
        return group_by_view{std::forward<Rng>(r), std::forward<Pred>(p)};
    }
    static constexpr int _S_arity = 2;
    using views::__adaptor::_RangeAdaptor<_Group_by>::operator();
};
inline constexpr _Group_by group_by;
#else
inline constexpr views::__adaptor::_RangeAdaptor group_by
    = [] <ranges::viewable_range Rng, typename Pred> (Rng&& r, Pred&& p) {
        return group_by_view{std::forward<Rng>(r), std::forward<Pred>(p)};
};
#endif


///////////////////////////////////////////////////////////////////////////////
// concat_view
template<ranges::viewable_range... Rngs>
requires (sizeof...(Rngs) > 1)
struct concat_view {
    static constexpr size_t nRngs = sizeof...(Rngs);
    using RNGs = std::tuple<Rngs...>;
    concat_view() = default;
    explicit concat_view(Rngs... rngs): rngs{std::move(rngs)...}
    { }

    struct ConcatIterator {
        using difference_type = std::ptrdiff_t;
        using value_type = std::common_type_t<ranges::range_reference_t<Rngs>...>;

        ConcatIterator() = default;
        ConcatIterator(RNGs* rngs): rngs(rngs)
        { its.template emplace<0>(ranges::begin(std::get<0>(*rngs))); }

        ConcatIterator& operator++() {
            // TODO: check empty range, skip it
            std::visit([&](auto&& iter) {
                constexpr size_t idx = iter.value;
                if ( (iter.iterator = ranges::next(iter.iterator))
                        == ranges::end(std::get<idx>(*rngs)) ) {
                    if constexpr (idx + 1 < nRngs) {
                        its.template emplace<idx + 1>(
                            ranges::begin(std::get<idx + 1>(*rngs))
                        );
                    }
                }
            }, its);
            return *this;
        }
        ConcatIterator operator++(int) {
            ConcatIterator tmp(*this);
            ++*this;
            return tmp;
        }
        using reference = std::common_reference_t<ranges::range_reference_t<Rngs>...>;
        reference operator*() const {
            return std::visit([](auto&& iter) -> reference {
                return *iter.iterator;
            }, its);
        }

        friend bool operator==(const ConcatIterator& iter, std::default_sentinel_t) {
            return iter.its.index() == nRngs - 1 &&
                   (std::get<nRngs - 1>(iter.its).iterator ==
                    ranges::end(std::get<nRngs - 1>(*iter.rngs)));
        }

        friend bool operator==(const ConcatIterator& lhs, const ConcatIterator& rhs) = default;

    private:
        template<size_t N, typename Rng>
        struct IteratorWithIndex: std::integral_constant<size_t, N> {
            IteratorWithIndex() = default;
            IteratorWithIndex(ranges::iterator_t<Rng> iterator):
                iterator(std::move(iterator)) {}
            ranges::iterator_t<Rng> iterator;
            friend bool operator==(const IteratorWithIndex& lhs, const IteratorWithIndex& rhs) = default;
        };

        template<size_t ...Is>
        static constexpr auto iteratorVariantGenerator(std::index_sequence<Is...>)
            -> std::variant<IteratorWithIndex<Is, std::tuple_element_t<Is, RNGs>>...>;

        decltype(iteratorVariantGenerator(std::make_index_sequence<nRngs>{})) its;
        RNGs* rngs {};
    };

    ConcatIterator begin() { return {&this->rngs}; }

    std::default_sentinel_t end() { return {}; }

private:
    RNGs rngs;
};

#if __GNUC__ > 10
struct _Concat: views::__adaptor::_RangeAdaptor<_Concat> {
    template<ranges::viewable_range... Rngs>
    constexpr auto operator()(Rngs&&... rngs) const {
        return concat_view{std::forward<Rngs>(rngs)...};
    }
    using views::__adaptor::_RangeAdaptor<_Concat>::operator();
};

inline constexpr _Concat concat;
#else
inline constexpr views::__adaptor::_RangeAdaptor concat
    = [] <ranges::viewable_range... Rngs> (Rngs&&... rngs) {
        return concat_view{std::forward<Rngs>(rngs)...};
};
#endif
///////////////////////////////////////////////////////////////////////////////
// repeat_n
template<typename Value>
struct repeat_n_view: ranges::view_interface<repeat_n_view<Value>> {
    repeat_n_view() = default;
    repeat_n_view(size_t n, Value value): n(n), value(std::move(value)) { }

private:
    struct RepeatIterator {
        using difference_type = std::ptrdiff_t;
        using value_type = Value;

        RepeatIterator& operator++() { --n; return *this; }
        RepeatIterator operator++(int) {
            RepeatIterator tmp(*this);
            ++*this;
            return tmp;
        }
        decltype(auto) operator*() const { return *value; }

        friend bool operator==(const RepeatIterator& iter, std::default_sentinel_t)
        { return iter.n == 0; }

        friend bool operator==(const RepeatIterator& lhs, const RepeatIterator& rhs) = default;

        size_t n{};
        Value* value{};
    };

public:
    RepeatIterator begin() { return {n, &value}; }
    std::default_sentinel_t end() { return {}; }

private:
    size_t n;
    Value value;
};

inline constexpr auto repeat_n = []<typename Value>(Value&& value, size_t n) {
    return repeat_n_view{n, std::forward<Value>(value)};
};

///////////////////////////////////////////////////////////////////////////////
// join
#if __GNUC__ > 10
struct _Join: views::__adaptor::_RangeAdaptorClosure {
    template<ranges::viewable_range Rng>
    constexpr auto operator()(Rng&& rng) const {
        using RangeValue = ranges::range_value_t<Rng>;
        using JoinResult = std::conditional_t<
            ranges::range<RangeValue> && !ranges::view<RangeValue>, // is container?
            RangeValue,
            std::vector<ranges::range_value_t<RangeValue>>
        >;
        JoinResult res;
        for (auto&& r: rng) {
            res.insert(ranges::end(res),
                    ranges::begin(r),
                    ranges::end(r));
        }
        return res;
    }
};
inline constexpr _Join join;
#else
inline constexpr views::__adaptor::_RangeAdaptorClosure join
    = [] <ranges::viewable_range Rng> (Rng&& rng) {
    using RangeValue = ranges::range_value_t<Rng>;
    using JoinResult = std::conditional_t<
        ranges::range<RangeValue> && !ranges::view<RangeValue>, // is container?
        RangeValue,
        std::vector<ranges::range_value_t<RangeValue>>
    >;
    JoinResult res;
    for (auto&& r: rng) {
        res.insert(ranges::end(res),
                ranges::begin(r),
                ranges::end(r));
    }
    return res;
};
#endif

///////////////////////////////////////////////////////////////////////////////
// chunk_view
template<ranges::viewable_range Rng>
struct chunk_view: ranges::view_interface<chunk_view<Rng>> {
    chunk_view() = default;
    chunk_view(Rng r, size_t n): r(std::move(r)), n(n) {}

private:
    struct ChunkIterator {
        using difference_type = std::ptrdiff_t;
        using value_type = ranges::subrange<ranges::iterator_t<Rng>>;

        ChunkIterator& operator++() {
            cur = next_cur;
            if (cur != last) {
                next_cur = ranges::next(cur, n, last);
            }
            return *this;
        }
        ChunkIterator operator++(int) {
            ChunkIterator tmp(*this);
            ++*this;
            return tmp;
        }
        value_type operator*() const { return {cur, next_cur}; }

        friend bool operator==(const ChunkIterator& iter, std::default_sentinel_t)
        { return iter.cur == iter.last; }

        friend bool operator==(const ChunkIterator& lhs, const ChunkIterator& rhs) {
            return lhs.cur == rhs.cur;
        }

        size_t n{};
        ranges::iterator_t<Rng> cur{};
        ranges::iterator_t<Rng> next_cur{};
        ranges::sentinel_t<Rng> last{};
    };
public:

    ChunkIterator begin() {
        auto beg = ranges::begin(r);
        auto end = ranges::end(r);
        return {n, beg, ranges::next(beg, n, end), end};
    }

    std::default_sentinel_t end() { return {}; }

    Rng r;
    size_t n;
};

#if __GNUC__ > 10
struct _Chunk: views::__adaptor::_RangeAdaptor<_Chunk> {
    template<ranges::viewable_range Rng>
    constexpr auto operator()(Rng&& r, size_t n) const {
        return chunk_view{std::forward<Rng>(r), n};
    }
    static constexpr int _S_arity = 2;
    using views::__adaptor::_RangeAdaptor<_Chunk>::operator();
};
inline constexpr _Chunk chunk;
#else
inline constexpr views::__adaptor::_RangeAdaptor chunk
    = [] <ranges::viewable_range Rng> (Rng&& r, size_t n) {
        return chunk_view{std::forward<Rng>(r), n};
};
#endif


///////////////////////////////////////////////////////////////////////////////
// interleave_view
template<ranges::viewable_range Rng>
struct interleave_view: ranges::view_interface<interleave_view<Rng>> {
    using RNGs = std::vector<ranges::range_value_t<Rng>>;
    interleave_view() = default;
    interleave_view(Rng rng) {
        for(auto&& r: rng) { rngs.emplace_back(r); }
    }

private:
    struct InterleaveInterator {
        using difference_type = std::ptrdiff_t;
        using value_type = ranges::range_value_t<ranges::range_value_t<Rng>>;

        InterleaveInterator() = default;
        InterleaveInterator(RNGs* rngs): rngs(rngs) {
            for (auto&& r: *rngs) {
                its.emplace_back(ranges::begin(r));
            }
        }

        InterleaveInterator& operator++() {
            if ( ((++n) %= its.size()) == 0) {
                ranges::for_each(its, [](auto&& iter) {
                    iter = ranges::next(iter);
                });
            }
            return *this;
        }
        InterleaveInterator operator++(int) {
            InterleaveInterator tmp(*this);
            ++*this;
            return tmp;
        }
        decltype(auto) operator*() const { return *its[n]; }

        friend bool operator==(const InterleaveInterator& iter, std::default_sentinel_t) {
            if (iter.n != 0) return false;
            auto ends = *iter.rngs | views::transform(ranges::end);
            return ranges::mismatch(iter.its, ends, std::equal_to<>{}).in1 == iter.its.end();
        }

        friend bool operator==(const InterleaveInterator& lhs, const InterleaveInterator& rhs) = default;

        size_t n{};
        std::vector<ranges::iterator_t<ranges::range_value_t<Rng>>> its;
        RNGs* rngs{};
    };
public:

    InterleaveInterator begin() { return {&rngs}; }
    std::default_sentinel_t end() { return {}; }

    RNGs rngs;
};

#if __GNUC__ > 10
struct _Interleave: views::__adaptor::_RangeAdaptorClosure {
    template<ranges::viewable_range Rng>
    constexpr auto operator()(Rng&& r) const {
        return interleave_view{std::forward<Rng>(r)};
    }
};
inline constexpr _Interleave interleave;
#else
inline constexpr views::__adaptor::_RangeAdaptorClosure interleave
    = [] <ranges::viewable_range Rng> (Rng&& r) {
        return interleave_view{std::forward<Rng>(r)};
};
#endif

///////////////////////////////////////////////////////////////////////////////
// transpose
#if __GNUC__ > 10
struct _Transpose: views::__adaptor::_RangeAdaptorClosure {
    template<ranges::viewable_range Rng>
    constexpr auto operator()(Rng&& r) const {
        return r | interleave | chunk(ranges::distance(r));
    }
};
inline constexpr _Transpose transpose;
#else
inline constexpr views::__adaptor::_RangeAdaptorClosure transpose
    = [] <ranges::viewable_range Rng> (Rng&& r) {
    return r | interleave | chunk(ranges::distance(r));
};
#endif
支持一下
扫一扫,支持Netcan
  • 微信扫一扫
  • 支付宝扫一扫