What would be the most concise and/or idiomatic way to create a std::string
from a transformed view (of another string)?
The best I could come up is to use std::ranges::copy
with the view and inserter as arguments, and even that is only accepted by gcc (10.2 & trunk) but not clang 11.x.
For most ranges
algorithms, I thought the intent was that the operator|
can be used whenever the first argument to a function is a range, so I wonder why the 2nd and IMHO cleaner variant doesn't compile (the error message is that no matching copy
was found), and is the intent to maybe eventually have it compile, or what?
I tried with both gcc 10.2 and gcc (11) trunk. Clang doesn't compile the first form either :(
#include <algorithm>
#include <cctype>
#include <iterator>
#include <ranges>
#include <string>
int main() {
std::string original = "foo";
std::string upper;
// This compiles on gcc 10.2/11-trunk only
std::ranges::copy(
original
| std::ranges::views::transform([](char c){return std::toupper(c);}),
std::back_inserter(upper)
);
// The below doesn't compile period
original
| std::ranges::views::transform([](char c){return std::toupper(c);})
| std::ranges::copy(std::back_inserter(upper));
}
For most ranges algorithms, I thought the intent was that the
operator|
can be used whenever the first argument to a function is a range
This isn't quite right. operator|
is only provided for those algorithms for which the first argument is a range and the result is a range.
transform
, filter
, take
, etc., are all algorithms that take a range and return a range. More specifically, they take a viewable_range
and return a view
.
But for all the other algorithms - max
, find_if
, any_of
, etc. - that don't return a range, there is no |
alternative to them. So this:
original
| std::ranges::views::transform([](char c){return std::toupper(c);})
| std::ranges::copy(std::back_inserter(upper));
isn't even intended to work, because copy
is not one of the algorithms that returns a view
, so it doesn't get |
support. One of the motivations of the pipeline-rewrite operator proposal (P2011) is to be able to use such functionality (without the massive library machinery necessary to make it work).
On the other hand, this:
std::ranges::copy(
original
| std::ranges::views::transform([](char c){return std::toupper(c);}),
std::back_inserter(upper)
);
is perfectly valid code that is intended to work. This is caused by clang checking some concepts too eagerly (see bug 47509). I think there's another bug report too, just can't find it at the moment.
Eventually the ranges::to
from range-v3 will be adopted, so the intended idiomatic construction would be:
std::string upper =
original
| std::ranges::views::transform([](char c){return std::toupper(c);}),
| std::ranges::to<std::string>();
Which, until then, you can use range-v3 for to
.
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