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&
的字符串。然后,由于问题被标记为 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&
):
| 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)
计算value
在r
中的实例。没有一元的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 的 openjdk:8-alpine 替代方案