C++ Logo

std-proposals

Advanced search

Re: [std-proposals] ranges::to<view_type>?

From: Arthur O'Dwyer <arthur.j.odwyer_at_[hidden]>
Date: Sun, 15 Dec 2024 14:01:06 -0500
On Sun, Dec 15, 2024 at 10:47 AM Hewill Kang <hewillk_at_[hidden]> wrote:

> (1) It won't just construct T from Args...; it will do a whole complicated
>> overload resolution and possibly end up constructing T from
>> `std::from_range_t, Args...` or some other bag of arguments entirely, which
>> is going to be error-prone and confusing
>
>
> I don't think this is a disadvantage, it's actually the power of
> ranges::to IMO. because it can choose the appropriate way to construct the
> destination object. Note that ranges::to can specify *any* type other
> than view type, even non-range type, so I don't think removing the view
> restriction can introduce any significant confusion.
>
> (2) `ranges::to<T>` is constrained to work only when `T` is a non-view
>> range type, plus certain other constraints.
>
>
> ranges::to works with any non-view range class type, even
> ranges::to<tuple>. Removing the view restriction would make (2) work as it
> *should*.
> Also, C++26 makes optional a view, it turns out that we can no longer
> write ranges::to<optional>, which is unfortunate since we might want to
> transform vector<optional<int>> into optional<vector<int>>.
>
> If ranges::to can specify any non-view class type, then why do we just
> relax and allow it to specify any class type?
>

I'm not 100% sure; but I do notice that P1206
<https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p1206r7.pdf>
repeatedly
describes `ranges::to` as a way to construct *containers* from ranges.
And some container adaptors — e.g. std::stack, std::queue,
std::priority_queue — do not satisfy `std::ranges::range`. They are
constructible *from* ranges, but they are not themselves ranges.
If you want `ranges::to` to be able to construct any container, then it'd
be wrong to constrain it on `std::ranges::range`.

I'm intrigued by your observation that this worked in C++23:
    std::vector<int> v = {1, 2, 3};
    auto o1 = std::optional(v);
    auto o2 = v | std::ranges::to<std::optional>(); // OK in C++23, same
effect as o1
and stopped working in C++26. Cc'ing Barry Revzin, because I vaguely recall
that maybe he said something about ranges::to<optional> at some point.
Certainly he knows a lot about both `optional` and `ranges::to`, and might
be able to say whether this was ever an intended use-case of the two
together.
My opinion remains that this is *not* an intended use-case (optional is not
a container) and so you should not be surprised if ranges::to<optional>
stops working, just like ranges::to<fs::path> never worked in the first
place. Those aren't the intended usage of `ranges::to` (IMO).

I wonder whether there's LWG appetite for an editorial pull request
changing the identifier `C` in to `Container` in [range.utility.conv.to],
to match the existing name `Container` used in
[range.utility.conv.general]. The idea being to give a non-normative hint
that `ranges::to` isn't intended for usage with arbitrary destination types.
https://eel.is/c++draft/range.utility.conv

–Arthur


Arthur O'Dwyer <arthur.j.odwyer_at_[hidden]> 於 2024年12月14日 週六 上午6:21寫道:
>
>> On Fri, Dec 13, 2024 at 1:33 PM Hewill Kang via Std-Proposals <
>> std-proposals_at_[hidden]> wrote:
>>
>>> Currently, the template parameter C of ranges::to<C>(r)
>>> <https://en.cppreference.com/w/cpp/ranges/to> cannot be a view, which
>>> means we cannot write the following:
>>>
>>> string s;
>>> auto sv = ranges::to<string_view>(s); // error, string_view is view
>>>
>>> I find this to be very inconvenient, as such limitation really prohibits
>>> a lot of useful use cases:
>>>
>>> string_view s = "1.2.3.4";
>>> auto r = s | views::split('.')
>>> | views::transform(ranges::to<std::string_view>()); //
>>> error
>>>
>>
>> I'm not convinced that this is a proper use of `ranges::to`.
>> It *would* be a proper use of Avi Kivity's recently proposed
>> `std::construct`,
>> <https://lists.isocpp.org/std-proposals/2024/12/11641.php> which is
>> explicitly designed to "lift" a constructor into a callable, and for no
>> other purpose, as in:
>>
>> namespace std {
>> template<class T> struct construct {
>> static T operator()(auto&&... args) { return
>> T(decltype(args)(args)...); }
>> };
>> } // namespace std
>>
>> auto r = s | views::split('.') |
>> views::transform(std::construct<std::string_view>());
>>
>> Now, it's true that `ranges::to<T>` can already be used as a poor man's
>> version of `std::construct<T>`, but it suffers from two big limitations in
>> that respect:
>>
>> (1) It won't just construct T from Args...; it will do a whole
>> complicated overload resolution and possibly end up constructing T from
>> `std::from_range_t, Args...` or some other bag of arguments entirely, which
>> is going to be error-prone and confusing, if what you really want is
>> *just* to lift the constructor into a callable. That is, `ranges::to`
>> cannot be used as a poor man's std::construct *in generic code*.
>> (Still, it *can* be used *as `ranges::to`* in generic code!)
>>
>> (2) `ranges::to<T>` is constrained to work only when `T` is a non-view
>> range type, plus certain other constraints.
>> auto a = s | std::ranges::to<std::string>(); // OK
>> auto b = s | std::ranges::to<std::string_view>(); // ill-formed
>> SFINAE-friendly
>> auto c = s | std::ranges::to<std::filesystem::path>(); // ill-formed
>> hard-error
>>
>> Your proposal asks to patch up a very tiny piece of (2), while leaving
>> most of (2) and all of (1) unfixed. That doesn't seem like a good idea to
>> me.
>>
>> Let `ranges::to` be `ranges::to`; if you want a way to lift a constructor
>> into a callable, don't misuse `ranges::to` for that purpose, but rather,
>> design a new thing that does *only* that lifting.
>>
>> HTH,
>> –Arthur
>>
>

Received on 2024-12-15 19:01:20