简体   繁体   English

C++20 Streams 又名 Ranges

[英]C++20 Streams aka Ranges

When I use the Stream Library ( http://jscheiny.github.io/Streams/api.html# ) I can do similar things like in Java-Streams:当我使用 Stream 库( http://jscheiny.github.io/Streams/api-Streams# )时,我可以做类似的事情:

#include "Streams/source/Stream.h"
#include <iostream>

using namespace std;
using namespace stream;
using namespace stream::op;

int main() {

    list<string> einkaufsliste = {
        "Bier", "Käse", "Wurst", "Salami", "Senf", "Sauerkraut"
    };

    int c = MakeStream::from(einkaufsliste)
          | filter([] (string s) { return !s.substr(0,1).compare("S"); })
          | peek([] (string s) { cout << s << endl; })
          | count()
          ;

    cout << c << endl;
}

It gives this output:它给出了这个 output:

Salami
Senf
Sauerkraut
3

In C++20 I discovered ranges, which look promising to achieve the same thing.在 C++20 中,我发现了范围,它们看起来有望实现相同的目标。 However, when I want to build something similar functional programming style it does not work:但是,当我想构建类似的函数式编程风格时,它不起作用:

#include <iostream>
#include <ranges>
#include <vector>
#include <algorithm>

using namespace std;

int main() {

    vector<string> einkaufsliste = {
        "Bier", "Käse", "Wurst", "Salami", "Senf", "Sauerkraut"
    };

    int c = einkaufsliste
          | ranges::views::filter([] (string s) { return !s.substr(0,1).compare("S"); })
          | ranges::for_each([] (string s) { cout << s << " "; })
          | ranges::count();
          ;
}

Seams that the ranges thing is not meant to work like this, although articles like this ( https://www.modernescpp.com/index.php/c-20-the-ranges-library ) suggest such a feature.接缝范围的事情并不意味着像这样工作,尽管像这样的文章( https://www.modernescpp.com/index.php/c-20-the-ranges-library )提出了这样的功能。

test.cpp:16:67: note:   candidate expects 3 arguments, 1 provided
   16 |             | ranges::for_each([] (string s) { cout << s << " "; })
      |                                                                   ^
test.cpp:17:29: error: no match for call to '(const std::ranges::__count_fn) ()'
   17 |             | ranges::count();
      |                             ^

Any ideas how I still could do similar things in C++20?有什么想法我仍然可以在 C++20 中做类似的事情吗?

There is an issue with each adapter here.这里的每个适配器都有问题。


First, filter :首先, filter

| ranges::views::filter([] (string s) { return !s.substr(0,1).compare("S"); })

This copies every string, then creates a new string out of each one, all just to check if the first character is an S .这会复制每个字符串,然后从每个字符串中创建一个新字符串,所有这些都只是为了检查第一个字符是否为S You should definitely take the string by const& here.您绝对应该在这里通过const&获取字符串。 And then, since the question is tagged C++20:然后,由于问题被标记为 C++20:

| ranges::views::filter([](string const& s) { return !s.starts_with('S'); })

Second, for_each :其次, for_each

| ranges::for_each([] (string s) { cout << s << " "; })

ranges::for_each is not a range adapter - it is an algorithm that invokes the callable on each element, but it doesn't return a new range, so it can't fit in a pipeline like this. ranges::for_each不是范围适配器——它是一种在每个元素上调用可调用对象的算法,但它不返回新范围,因此它不适合这样的管道。

Ranges does not have a peek adapter like this (neither C++20 nor range-v3) But we could try to implement it in terms of a transform using identity: Ranges 没有像这样的peek适配器(C++20 和 range-v3 都没有)但是我们可以尝试通过使用标识的transform来实现它:

auto peek = [](auto f){
    return ranges::views::transform([=]<typename T>(T&& e) -> T&& {
        f(e);
        return std::forward<T>(e);
    });
};

And now you can write (and again, the string should be taken as const& here):现在你可以写了(同样,这里的字符串应该被当作const& ):

| peek([](std::string const& s){ cout << s << " "; })

But this will actually only work if we access any of the elements of the range, and nothing in your code has to do that (we don't need to read any of the elements to find the distance, we just need to advance the iterator as many times as necessary).但这实际上只有在我们访问范围的任何元素时才有效,并且您的代码中没有任何东西必须这样做(我们不需要读取任何元素来查找距离,我们只需要推进迭代器根据需要多次)。 So you'll find that the above doesn't actually print anything.所以你会发现上面实际上并没有打印任何东西。

So instead, for_each is the correct approach, we just have to do it separately:所以相反, for_each是正确的方法,我们只需要单独做:

auto f = einkaufsliste
       | ranges::views::filter([](string const& s) { return s.starts_with('S'); });
ranges::for_each(f, [](string const& s){ cout << s << " "; });

That will definitely print every element.肯定会打印每个元素。


Lastly, count :最后, count

| ranges::count();

In Ranges, only the adapters that return a view are pipeable.在 Ranges 中,只有返回视图的适配器是可管道的。 count() just doesn't work this way. count()只是不能这样工作。 Also, count() takes a second argument which is which thing you're counting.此外, count()接受第二个参数,您要计算的内容。 count(r, value) counts the instances of value in r . count(r, value)r中的value实例进行计数。 There is no unary count(r) .没有一元count(r)

The algorithm you're looking for is named distance (and likewise, is not pipeable into).您正在寻找的算法被命名为distance (同样,不能通过管道输入)。

So you'd have to write the whole thing like this:所以你必须像这样写整个事情:

int c = ranges::distance(f);

This was part of the motivation for P2011 , to be able to actually write count at the end in linear order rather than at the front (without having to make a lot more library changes).这是P2011动机的一部分,以便能够以线性顺序实际在末尾而不是在前面写入count (无需进行更多的库更改)。

Main problem with your attempt is that only views are "pipable", and algorithms such as std::ranges::for_each are not.您尝试的主要问题是只有视图是“pipable”的,而诸如std::ranges::for_each之类的算法则不是。

There isn't a necessity to cram all of the functionality into a single statement.没有必要将所有功能都塞进一个语句中。 Here is a correct way to do the same with C++20 ranges:这是对 C++20 范围执行相同操作的正确方法:

// note the use of std::string_view instead
// of std::string to avoid copying
auto starts_s_pred = [] (std::string_view s) {
    return s.starts_with("S");
};
auto starts_s_filtered =
    einkaufsliste
    | ranges::views::filter(starts_s_pred);

// we could use std::string_view too, but it is
// unnecessary to restrict the lambda argument
// since this can easily work with anything
// insertable into a string stream
auto print_line = [] (const auto& s) {
    std::cout << s << '\n';
};

ranges::for_each(starts_s_filtered, print_line);
std::cout << std::ranges::distance(starts_s_filtered);


// alternative to print_line and for_each
for (const auto& s : starts_s_filtered)
    std::cout << s << '\n';

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM