Date: Mon, 16 Dec 2024 01:14:48 +0800
Hi Arthur,
FYI:
https://stackoverflow.com/questions/73768399/why-does-c23-rangesto-not-constrain-the-container-type-c-to-be-a-range
Thanks,
Hewill
Arthur O'Dwyer <arthur.j.odwyer_at_[hidden]> 於 2024年12月16日 週一 上午3:01寫道:
> 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
>>>
>>
FYI:
https://stackoverflow.com/questions/73768399/why-does-c23-rangesto-not-constrain-the-container-type-c-to-be-a-range
Thanks,
Hewill
Arthur O'Dwyer <arthur.j.odwyer_at_[hidden]> 於 2024年12月16日 週一 上午3:01寫道:
> 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-16 02:50:27