Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create a new string from a range in C++20?

Tags:

c++

c++20

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));
}
like image 710
Kuba hasn't forgotten Monica Avatar asked Jan 21 '21 16:01

Kuba hasn't forgotten Monica


1 Answers

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.

like image 155
Barry Avatar answered Nov 01 '22 15:11

Barry