C++20 Streams 又名 Ranges

Posted

技术标签:

【中文标题】C++20 Streams 又名 Ranges【英文标题】:C++20 Streams aka Ranges 【发布时间】:2021-05-23 15:07:54 【问题描述】:

当我使用 Stream Library (http://jscheiny.github.io/Streams/api.html#) 时,我可以在 Java-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;


它给出了这个输出:

Salami
Senf
Sauerkraut
3

在 C++20 中,我发现了范围,它们看起来有望实现相同的目标。但是,当我想构建类似的函数式编程风格时,它不起作用:

#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();
          ;

范围事物的接缝并不意味着像这样工作,尽管像这样的文章 (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();
      |                             ^

任何想法我仍然可以在 C++20 中做类似的事情吗?

【问题讨论】:

【参考方案1】:

这里的每个适配器都有问题。


首先,filter

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

这会复制每个字符串,然后从每个字符串中创建一个新字符串,所有这些都是为了检查第一个字符是否为S。您绝对应该在这里使用const&amp; 的字符串。然后,由于问题被标记为 C++20:

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

第二,for_each

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

ranges::for_each 不是范围适配器 - 它是一种在每个元素上调用可调用对象的算法,但它不返回新范围,因此它不适合这样的管道。

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);
    );
;

现在你可以写了(同样,这里的字符串应该是const&amp;):

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

但这实际上只有在我们访问范围内的任何元素时才有效,而您的代码中不需要这样做(我们不需要读取任何元素来查找距离,我们只需要根据需要多次推进迭代器)。所以你会发现上面实际上并没有打印任何东西。

所以,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 << " "; );

这将绝对打印每个元素。


最后,count:

| ranges::count();

在 Ranges 中,只有返回视图的适配器是可管道的。 count() 只是不能这样工作。此外,count() 接受第二个参数,即 which 您正在计算的东西。 count(r, value) 计算valuer 中的实例。没有一元的count(r)

您要查找的算法名为 distance(同样,不可通过管道输入)。

所以你必须这样写:

int c = ranges::distance(f);

这是 P2011 的部分动机,以便能够以线性顺序在末尾而不是在前面实际写入 count(无需进行更多的库更改)。

【讨论】:

【参考方案2】:

您尝试的主要问题是只有视图是“可传输的”,而std::ranges::for_each 等算法则不是。

没有必要将所有功能都塞进一个语句中。这是对 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';

【讨论】:

以上是关于C++20 Streams 又名 Ranges的主要内容,如果未能解决你的问题,请参考以下文章

Kafka Streams开发入门

C# 8中的Async Streams

提供者使用 Streams 公开不正确的值

Kafka Streams 的 openjdk:8-alpine 替代方案

Ada - (Streams) 如何在事先不知道字符串长度的情况下正确调用 String'Read()

使用 Streams 生成 PDF [重复]