C++20 Ranges

01 引入范围的动机

C++17以前的标准库中大多数通用算法(如std::sort)采用一对迭代器操作数据。如:要对std::vector v进行排序,使用std::sort(v.begin(), v.end())而不是std::sort(v)1

这样的迭代器设计,有他的灵活性,它允许如:

仅对第五个元素之后的所有元素进行排序: std::sort(v.begin() + 5, v.end())
使用非标准的迭代器,例如反向迭代器:std::sort(v.rbegin() + 5, v.rend())以相反的顺序排序)

但是,此接口的直观性不如仅对要排序的实体调用std::sort,它还允许更多错误,例如,混合使用两个不兼容的迭代器。C ++ 20引入范围的概念,并提供在名称空间中接受范围的算法,例如,如果是范围std::ranges::,则可以这样使用:std::ranges::sort(v),而矢量v是范围!

那两个表明基于迭代器方法的优越性的例子又如何呢?在C ++ 20中,您可以执行以下操作:

仅对第五个元素之后的所有元素进行排序: std::ranges::sort(std::views::drop(v, 5))
反向排序: std::ranges::sort(std::views::reverse(v))

02 范围(ranges)

范围(ranges)通过增加一种叫做view(视图)的概念,实现Lazy Evaluation(惰性求值),并且可以将各种view的关系转化用符号“|”串联起来,提高代码的表现力和可读性。2

范围(range) 是“项目集合”或“可迭代事物”的抽象。最基本的定义只需要存在begin()和end()在范围内。STL的大部分容器都是range。

视图(view) 其意义可以参考string_view,它的拷贝代价是很低的,需要拷贝的时候直接传值即可,不必传引用。

view本身也符合range的特征,可以用迭代器遍历。

view对于数据的遍历都是lazy evaluation(惰性求值)。

范围适配器(range adaptor) ,可以将一个range转换为一个view(也可以将一个view转换为另一个view)。

范围适配器接受 viewable_range 为其第一参数并返回一个 view 。3

范围通常是输入范围(可以读取)或输出范围(可以写入)或两者兼而有之。例如,std::vector<int>是两者,但std::vector<int> const只能是输入范围。

输入范围具有不同的优势,这些优势可以通过更精细的概念来实现(即,模拟一个较强概念的类型,也总是模拟较弱概念的类型):4

概念描述
std::ranges::input_range可以从头到尾重复至少一次
std::ranges::forward_range可以从头到尾重复多次
std::ranges::bidirectional_range迭代器还可以向后移动
std::ranges::random_access_range您可以恒定时间跳转到元素
std::ranges::contiguous_range元素总是连续存储在内存中

03 range-v3库

2020年1月1日为止,vs2019中还没有提供完整的range功能。我们可以使用range-v3库来提前体验下range的功能。这个库应该是全覆盖了cppreference.com中范围库 (C++20)中介绍的内容。
range-v3库github地址:https://github.com/ericniebler/range-v3

具体的用法可以参考:将正式的range-v3与MSVC 2017 15.9版一起使用(Use the official range-v3 with MSVC 2017 version 15.9)

04 C++20 range demo

在vs2019中引入range-v3库,除需要下载range-v3的头文件外,还需要做一些配置。
配置属性==>C/C++==>常规==>附加包含目录中包含:.\range-v3\include
配置属性==>C/C++==>高级==>禁用特定警告中禁用:5105
配置属性==>C/C++==>命令行==>其它选项中添加:
/std:c++latest /permissive- /experimental:preprocessor
下面把range-v3的example整理到一个文件中。方便阅读。
代码在:https://github.com/5455945/cpp_demo/blob/master/C%2B%2B20/ranges/ranges.cpp

#include <array>
#include <chrono>
#include <deque>
#include <forward_list>
#include <list>
#include <iostream>
#include <set>
#include <string>
#include <map>
#include <unordered_map>
#include <unordered_set>
#include <vector>

#include <range/v3/all.hpp>
namespace views = ranges::views;
namespace Ranges = ranges;
using namespace std;

void range_test_01() {
    // https://zh.cppreference.com/w/cpp/ranges
    std::vector<int> ints{ 0,1,2,3,4,5 };
    auto even = [](int i) { return 0 == i % 2; };
    auto square = [](int i) { return i * i; };

    for (int i : ints | views::filter(even) | views::transform(square)) {
        std::cout << i << ' ';
    }
    cout << "\n";
}

// https://github.com/ericniebler/range-v3/blob/master/example/accumulate_ints.cpp
void range_test_02() {
    int sum = Ranges::accumulate(views::ints(1, Ranges::unreachable) | views::transform([](int i) {
    return i * i;
        }) | views::take(10),
            0);
    // prints: 385
    cout << sum << '\n';
}

// https://github.com/ericniebler/range-v3/blob/master/example/any_all_none_of.cpp
void range_test_03() {
    auto is_six = [](int i) { return i == 6; };
    std::vector<int> v{ 6, 2, 3, 4, 5, 6 };
    cout << std::boolalpha;
    cout << "vector: " << Ranges::views::all(v) << '\n';

    cout << "vector any_of is_six: " << Ranges::any_of(v, is_six) << '\n';
    cout << "vector all_of is_six: " << Ranges::all_of(v, is_six) << '\n';
    cout << "vector none_of is_six: " << Ranges::none_of(v, is_six) << '\n';
}

// https://github.com/ericniebler/range-v3/blob/master/example/comprehension_conversion.cpp
void range_test_04() {
    using namespace Ranges;
    auto vi = views::for_each(views::ints(1, 6),
        [](int i) { return yield_from(views::repeat_n(i, i)); }) |
        to<std::vector>();
    // prints: [1,2,2,3,3,3,4,4,4,4,5,5,5,5,5]
    cout << views::all(vi) << '\n';
}

// https://github.com/ericniebler/range-v3/blob/master/example/comprehensions.cpp

//  Benchmark Code

class timer
{
private:
    std::chrono::high_resolution_clock::time_point start_;

public:
    timer()
    {
        reset();
    }
    void reset()
    {
        start_ = std::chrono::high_resolution_clock::now();
    }
    std::chrono::milliseconds elapsed() const
    {
        return std::chrono::duration_cast<std::chrono::milliseconds>(
            std::chrono::high_resolution_clock::now() - start_);
    }
    friend std::ostream& operator<<(std::ostream& sout, timer const& t)
    {
        return sout << t.elapsed().count() << "ms";
    }
};

void benchmark()
{
    // Define an infinite range containing all the Pythagorean triples:
    auto triples = views::for_each(views::iota(1), [](int z) {
        return views::for_each(views::iota(1, z + 1), [=](int x) {
            return views::for_each(views::iota(x, z + 1), [=](int y) {
                return Ranges::yield_if(x * x + y * y == z * z,
                    std::make_tuple(x, y, z));
                });
            });
        });

    static constexpr int max_triples = 3000;

    timer t;
    int result = 0;
    RANGES_FOR(auto triple, triples | views::take(max_triples))
    {
        int i, j, k;
        std::tie(i, j, k) = triple;
        result += (i + j + k);
    }
    std::cout << t << '\n';
    std::cout << result << '\n';

    result = 0;
    int found = 0;
    t.reset();
    for (int z = 1;; ++z)
    {
        for (int x = 1; x <= z; ++x)
        {
            for (int y = x; y <= z; ++y)
            {
                if (x * x + y * y == z * z)
                {
                    result += (x + y + z);
                    ++found;
                    if (found == max_triples)
                        goto done;
                }
            }
        }
    }
done:
    std::cout << t << '\n';
    std::cout << result << '\n';
}

void range_test_05() {
    // Define an infinite range containing all the Pythagorean triples:
    auto triples = views::for_each(views::iota(1), [](int z) {
        return views::for_each(views::iota(1, z + 1), [=](int x) {
            return views::for_each(views::iota(x, z + 1), [=](int y) {
                return Ranges::yield_if(x * x + y * y == z * z,
                    std::make_tuple(x, y, z));
                });
            });
        });

     This alternate syntax also works:
    // auto triples = iota(1)      >>= [] (int z) { return
    //                iota(1, z+1) >>= [=](int x) { return
    //                iota(x, z+1) >>= [=](int y) { return
    //    yield_if(x*x + y*y == z*z,
    //        std::make_tuple(x, y, z)); };}; };

    // Display the first 100 triples
    RANGES_FOR(auto triple, triples | views::take(100))
    {
        std::cout << '(' << std::get<0>(triple) << ',' << std::get<1>(triple)
            << ',' << std::get<2>(triple) << ')' << '\n';
    }
}

// https://github.com/ericniebler/range-v3/blob/master/example/count.cpp
void range_test_06() {
    std::vector<int> v{ 6, 2, 3, 4, 5, 6 };
    // note the count return is a numeric type
    // like int or long -- auto below make sure
    // it matches the implementation
    auto c = Ranges::count(v, 6);
    cout << "vector:   " << c << '\n';

    std::array<int, 6> a{ 6, 2, 3, 4, 5, 6 };
    c = Ranges::count(a, 6);
    cout << "array:    " << c << '\n';
}

// https://github.com/ericniebler/range-v3/blob/master/example/count_if.cpp
void range_test_07() {
    auto is_six = [](int i) -> bool { return i == 6; };

    std::vector<int> v{ 6, 2, 3, 4, 5, 6 };
    auto c = Ranges::count_if(v, is_six);
    cout << "vector:   " << c << '\n'; // 2

    std::array<int, 6> a{ 6, 2, 3, 4, 5, 6 };
    c = Ranges::count_if(a, is_six);
    cout << "array:    " << c << '\n'; // 2
}

// https://github.com/ericniebler/range-v3/blob/master/example/filter_transform.cpp
void range_test_08() {
    std::vector<int> const vi{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    auto rng = vi | views::filter([](int i) { return i % 2 == 0; }) |
        views::transform([](int i) { return std::to_string(i); });
    // prints: [2,4,6,8,10]
    cout << rng << '\n';
}

// https://github.com/ericniebler/range-v3/blob/master/example/find.cpp
void range_test_09() {

    auto is_six = [](int i) -> bool { return i == 6; };

    cout << "vector:   ";

    std::vector<int> v{ 6, 2, 6, 4, 6, 1 };
    {
        auto i = Ranges::find(v, 6); // 1 2 3 4 5 6
        cout << "*i: " << *i << '\n';
    }
    {
        auto i = Ranges::find(v, 10); // 1 2 3 4 5 6
        if (i == Ranges::end(v))
        {
            cout << "didn't find 10\n";
        }
    }
    {
        auto i = Ranges::find_if(v, is_six);
        if (i != Ranges::end(v))
        {
            cout << "*i: " << *i << '\n';
        }
    }
    {
        auto i = Ranges::find_if_not(v, is_six);
        if (i != Ranges::end(v))
        {
            cout << "*i: " << *i << '\n';
        }
    }
    {
        auto i = Ranges::find(v, 6);
        i++;
        if (i != Ranges::end(v))
        {
            cout << "*i after ++ (2 expected): " << *i;
        }
    }

    cout << "\narray:    ";

    std::array<int, 6> a{ 6, 2, 3, 4, 5, 1 };
    {
        auto i = Ranges::find(a, 6);
        if (i != Ranges::end(a))
        {
            cout << "*i: " << *i;
        }
    }
    cout << "\nlist:     ";

    std::list<int> li{ 6, 2, 3, 4, 5, 1 };
    {
        auto i = Ranges::find(li, 6);
        if (i != Ranges::end(li))
        {
            cout << "*i: " << *i;
        }
    }

    cout << "\nfwd_list: ";
    
    std::forward_list<int> fl{ 6, 2, 3, 4, 5, 1 };
    {
        auto i = Ranges::find(fl, 4);
        if (i != Ranges::end(fl))
        {
            cout << "*i: " << *i;
        }
    }
    cout << "\ndeque:    ";

    std::deque<int> d{ 6, 2, 3, 4, 5, 1 };
    {
        auto i = Ranges::find(d, 6);
        if (i != Ranges::end(d))
        {
            cout << "*i: " << *i;
        }
    }
    cout << '\n';
}

// https://github.com/ericniebler/range-v3/blob/master/example/for_each_assoc.cpp
auto print = [](int i) { cout << i << ' '; };
// must take a pair for map types
auto printm = [](std::pair<string, int> p) {
    cout << p.first << ":" << p.second << ' ';
};
void range_test_10() {
    cout << "set:           ";
    std::set<int> si{ 1, 2, 3, 4, 5, 6 };
    Ranges::for_each(si, print);

    cout << "\nmap:           ";
    std::map<string, int> msi{ {"one", 1}, {"two", 2}, {"three", 3} };
    Ranges::for_each(msi, printm);

    cout << "\nunordered map: ";
    std::unordered_map<string, int> umsi{ {"one", 1}, {"two", 2}, {"three", 3} };
    Ranges::for_each(umsi, printm);

    cout << "\nunordered set: ";
    std::unordered_set<int> usi{ 1, 2, 3, 4, 5, 6 };
    Ranges::for_each(usi, print);
    cout << '\n';
}

// https://github.com/ericniebler/range-v3/blob/master/example/for_each_sequence.cpp
void range_test_11() {
    cout << "vector:   ";
    std::vector<int> v{ 1, 2, 3, 4, 5, 6 };
    Ranges::for_each(v, print); // 1 2 3 4 5 6

    cout << "\narray:    ";
    std::array<int, 6> a{ 1, 2, 3, 4, 5, 6 };
    Ranges::for_each(a, print);

    cout << "\nlist:     ";
    std::list<int> ll{ 1, 2, 3, 4, 5, 6 };
    Ranges::for_each(ll, print);

    cout << "\nfwd_list: ";
    std::forward_list<int> fl{ 1, 2, 3, 4, 5, 6 };
    Ranges::for_each(fl, print);

    cout << "\ndeque:    ";
    std::deque<int> d{ 1, 2, 3, 4, 5, 6 };
    Ranges::for_each(d, print);
    cout << '\n';
}

// https://github.com/ericniebler/range-v3/blob/master/example/hello.cpp
void range_test_12() {
    std::string s{ "hello" };

    // output: h e l l o
    Ranges::for_each(s, [](char c) { cout << c << ' '; });
    cout << '\n';
}

// https://github.com/ericniebler/range-v3/blob/master/example/is_sorted.cpp
void range_test_13() {
    cout << std::boolalpha;
    std::vector<int> v{ 1, 2, 3, 4, 5, 6 };
    cout << "vector:   " << Ranges::is_sorted(v) << '\n';

    std::array<int, 6> a{ 6, 2, 3, 4, 5, 6 };
    cout << "array:    " << Ranges::is_sorted(a) << '\n';
}

// https://github.com/ericniebler/range-v3/blob/master/example/sort_unique.cpp
void range_test_14() {
    std::vector<int> vi{ 9, 4, 5, 2, 9, 1, 0, 2, 6, 7, 4, 5, 6, 5, 9, 2, 7,
                    1, 4, 5, 3, 8, 5, 0, 2, 9, 3, 7, 5, 7, 5, 5, 6, 1,
                    4, 3, 1, 8, 4, 0, 7, 8, 8, 2, 6, 5, 3, 4, 5 };
    vi |= Ranges::actions::sort | Ranges::actions::unique;
    // prints: [0,1,2,3,4,5,6,7,8,9]
    cout << views::all(vi) << '\n';
}

void range_test_15() {
    std::vector vec{ 1,2,3,4,5,6 };
    auto v = vec | views::reverse | views::drop(2);
    std::cout << *v.begin() << '\n';
    *v.begin() = 42;
    cout << v << '\n';
    for (auto i : vec) {
        cout << i << ' ';
    }
    cout << '\n';
}

int main()
{
    range_test_01();
    range_test_02();
    range_test_03();
    range_test_04();
    range_test_05();
    range_test_06();
    range_test_07();
    range_test_08();
    range_test_09();
    range_test_10();
    range_test_11();
    range_test_12();
    range_test_13();
    range_test_14();
    range_test_15();
    return 0;
}

  1. 来自SeqAn3 ↩︎

  2. 来自ranges for C++20简介 ↩︎

  3. 来自范围库 (C++20) ↩︎

  4. 来自SeqAn3 ↩︎

  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值