C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Fwd: ranges::collect

From: Corentin Adam <corentin.adam27_at_[hidden]>
Date: Sat, 28 Sep 2024 06:12:10 +0200
Thanks for your reply!

First of all, indeed adding `from_range` to the constructor makes sense.

Secondly, you seem to understand the proposal perfectly and your `when_all`
example summarizes the same algorithm as what I've called
`ranges::collect`.
However, `ranges::collect` has more features that I think are useful. For
example, `ranges::collect` works with any potential_type (i.e. any type
that works like `std::optional` or `std::expected`), can build any
container and you can pass container arguments as arguments to the function
(as I mentioned, this is really inspired by ranges::to).
You can read more about these capabilities in the README of my GitHub repos
here
<https://github.com/jileventreur/collect/blob/master/README.md#possibilities>
.

As for the function name itself, I'm not entirely convinced that `collect`
is the most expressive, but on the other hand, I think `when_all` doesn't
emphasize the container-building action performed by the function. But this
is a secondary point that should be discussed later if discussions on the
proposal go further.

Concerning `when_any`, it's true that it's more or less the opposite of
`collect` or `when_all`, but it adds real value to add this function
compared to a simple find.

I think that `ranges::collect/when_all` or the equivalent constructor
approach suggested by Barry is the most common way of handling a range of
“potential types”: if an error occurs, we abandon the process and handle
the error.
The other two possibilities, in my opinion, are:
 * ignore errors and collect valid values (and this is made easy by `p3168r1
<https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p3168r1.html>:
range support for optional` as seen in the first example shown in the paper)
 * separate errors and valid values and process them separately. This can
already be done easily with std::partition.

I agree with you that the constructor used in the ranges::to approach
suggested by Barry can be confusing for the user and I tend to prefer the
“when_all/collect” free function approach I suggested in the first place
for the time being because it is more explicit for users.
However, to be fair, I looked into the rust `collect` function to
understand how it works on optional or expected and it is indeed done using
a “constructor” (actually an implementation of the FromIterator trait)
implemented in their equivalent expected/optional types.
So `collect` in Rust works exactly like std::ranges::to and the “when_all”
algorithm I want to add is done in Rust in the way Barry suggests.
In Rust, this is common usage and anyone who calls collect on a
`range<optional<value>>` to create an `optional<container<value>` knows
that the result construction will follow the `when_all` behavior. Perhaps
this would be less obvious to cpp developers, as it's not a language
feature from scratch as it is in Rust.
About `std::ranges::to<std::optional>()` this CTAD behavior might simply be
unallowed I think (i.e. no CTAD for this optional/expected constructor) so
I'm not sure that invalidates the constructor approach. If you fully
specify the form of the output (e.g.
`std::ranges::to<optional<vector<int>>>()`), the “when_all” behavior will
occur. And because of the function's output type, it's impossible to expect
the when_any behavior you mentioned..

In any case, do you think either approach is worth proposing? I still think
it's generally necessary when working with a “potential_type” range, and
that it's missing from the current standard. If it's worthwhile, what
should I do next, and would someone more experienced than me be so kind as
to help me with this process?

Thank you,

Corentin Adam

Le ven. 27 sept. 2024 à 23:06, Arthur O'Dwyer <arthur.j.odwyer_at_[hidden]> a
écrit :

> On Thu, Sep 26, 2024 at 5:07 PM Corentin Adam via Std-Proposals <
> std-proposals_at_[hidden]> wrote:
>
>> I hadn't thought of it that way, but I agree with you that it would work
>> too.
>> If I understand correctly, you mean some range constructors like this
>> <https://godbolt.org/z/KGvaGboG7>?
>>
>
> Presumably more like this <https://godbolt.org/z/Tbe1jcvEr>. (The only
> diff is the addition of `from_range_t`.)
>
> IIUC, the proposal is to take a range of optionals and collapse it into an
> optional of range, with this signature and semantics (if not this exact
> name):
>
> std::optional<std::vector<T>> when_all(std::vector<std::optional<T>> v) {
> if (std::ranges::all_of(v, [](auto&& x) { return bool(x); })) {
> return v | std::views::transform([](auto&& o) { return *o; }) |
> std::ranges::to<std::vector<T>>();
> } else {
> return std::nullopt;
> }
> }
>
> The obvious next step after `when_all` is `when_any`:
>
> std::optional<T> when_any(std::vector<std::optional<T>> v) {
> auto it = std::ranges::find(v, true);
> return (it == v.end()) ? *it : std::nullopt;
> }
>
> Now, I don't think either of these is suitable for `ranges::to`, because
> then what would happen when I write `std::ranges::to<std::optional>()`?
> Just specifying the *shape of the output* isn't sufficient to tell me (as
> the code-writer or as the code-reader/reviewer) what *algorithm* I want
> (or expect) to be used to get the data into that shape. When the algorithm
> is non-obvious, and/or when there's more than one possible algorithm that
> could be used, the Standard Library should explicitly name the algorithm.
>
>
> Coincidentally, it was noted on the cpplang Slack just a day or two ago
> that
> // https://godbolt.org/z/1a6xcohTn
> char hello[] = "hello";
> auto s1 = hello | std::ranges::to<std::string>();
> auto s2 = hello | std::views::all | std::ranges::to<std::string>();
> assert(s1 != s2);
> which is kind of another case of "when there's multiple possible ways to
> do a transformation, it's confusing to give them all the same name."
>
> –Arthur
>
>
>
> On Thu, Sep 26, 2024 at 5:07 PM Corentin Adam via Std-Proposals <
> std-proposals_at_[hidden]> wrote:
>
>> I hadn't thought of it that way, but I agree with you that it would work
>> too.
>> If I understand correctly, you mean some range constructors like this
>> <https://godbolt.org/z/KGvaGboG7>?
>>
>> I've searched through the current proposals and it seems to me that these
>> constructors haven't been proposed yet (even in the optional range
>> support proposal), have they?
>> If they haven't yet been proposed, what do you think about drafting a
>> proposal to include them?
>>
>> Another question, why favor the constructor from ranges approach over the
>> collect free function approach?
>>
>> Le mer. 11 sept. 2024 à 19:47, Barry Revzin <barry.revzin_at_[hidden]> a
>> écrit :
>>
>>> On Wed, Sep 11, 2024, 11:56 AM Corentin Adam via Std-Proposals <
>>> std-proposals_at_[hidden]> wrote:
>>>
>>>>
>>>> I've recently been working on a cpp implementation of the rust collect
>>>> function: :
>>>> https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.collect.
>>>>
>>>> This function has two main uses in rust:
>>>> * construct containers from ranges (exactly the work achieved by
>>>> std::ranges::to)
>>>> * collect ranges of expected/optional values into expected/optional
>>>> range of value. Thus, if all values in the range are correct (in other
>>>> words has_value() == true), the return value is true and contains the range
>>>> of underlying values. Otherwise, the return value contains the first error
>>>> encountered in the range.
>>>>
>>>
>>> ranges::to can support the second use-case as well. We just need to add
>>> the appropriate constructor to optional and expected.
>>>
>>> Barry
>>>
>>>> --
>> Std-Proposals mailing list
>> Std-Proposals_at_[hidden]
>> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>>
>

Received on 2024-09-28 04:12:25