如何解决C++20 流又名范围
当我使用流库 (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","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 中做类似的事情吗?
解决方法
这里的每个适配器都有问题。
首先,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()
接受第二个参数,即您正在计算的哪个。 count(r,value)
计算 value
中 r
的实例数。没有一元 count(r)
。
您要查找的算法名为 distance
(同样,不能通过管道传输)。
所以你必须像这样写整个事情:
int c = ranges::distance(f);
这是 P2011 的部分动机,能够实际以线性顺序在末尾而不是在前面编写 count
(无需进行更多的库更改)。
您尝试的主要问题是只有视图是“可管道化的”,而 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';
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。