When I use the Stream Library (http://jscheiny.github.io/Streams/api.html#) I can do similar things like in 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;
}
It gives this output:
Salami
Senf
Sauerkraut
3
In C++20 I discovered ranges, which look promising to achieve the same thing. 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.
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?
There is an issue with each adapter here.
First, 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
. You should definitely take the string by const&
here. And then, since the question is tagged C++20:
| ranges::views::filter([](string const& s) { return !s.starts_with('S'); })
Second, 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 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:
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):
| 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:
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
:
| ranges::count();
In Ranges, only the adapters that return a view are pipeable. count()
just doesn't work this way. Also, count()
takes a second argument which is which thing you're counting. count(r, value)
counts the instances of value
in r
. There is no unary count(r)
.
The algorithm you're looking for is named distance
(and likewise, is not pipeable into).
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).
Main problem with your attempt is that only views are "pipable", and algorithms such as std::ranges::for_each
are not.
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:
// 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';
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With