5

当我使用 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 中做类似的事情吗?

4

2 回答 2

5

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


首先,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 都没有)但是我们可以尝试根据transformusing 标识来实现它:

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)计算valuein的实例r。没有一元count(r)的。

您正在寻找的算法被命名distance(同样,不能通过管道输入)。

所以你必须像这样写整个事情:

int c = ranges::distance(f);

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

于 2021-02-20T23:58:19.477 回答
2

您尝试的主要问题是只有视图是“pipable”的,而诸如此类的算法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';
于 2021-02-20T23:58:30.550 回答