C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Return type of string_view::remove_suffix

From: Arthur O'Dwyer <arthur.j.odwyer_at_[hidden]>
Date: Sat, 7 Jan 2023 18:13:33 -0500
On Sat, Jan 7, 2023 at 2:00 PM Giuseppe D'Angelo via Std-Proposals <
std-proposals_at_[hidden]> wrote:

> On 06/01/2023 17:28, Jason McKesson via Std-Proposals wrote:
> >> so that we can do stuff like:
> >>
> >> string str("monkey5");
> >> Func( string_view(str).remove_suffix(1u).remove_prefix(2u) );
> > You could just do this:
> >
> > Func(string_view(str.begin()+1, str.end() - 2));
> >
> > This requires C++20 for the contiguous iterator support, but it does
> > work. Note that the remove_* functions exhibit UB if you try to remove
> > more characters than exist. So the fact that this too will exhibit UB
> > in those situations is fine.
>
> It's completely anti-ergonomic to have to do math like that.
>
> Compare with something like Qt, where string classes have functions that
> mutate in place and functions that return the mutated version:
> view.chop(N); // mutates in-place, returns void
> view.chopped(N); // does not mutate, returns a new view
>

I agree with all three guidelines stated so far:
(1) Math is un-ergonomic.
(2) Active-verb and past-tense/adjectival-form (sort/sorted, chop/chopped,
strip/stripped, trim/trimmed) are the Right Solution.
(3) Having a method that *both* mutates *and* returns reference-to-*this is
just asking for misuse, and should be avoided at all costs.

But this particular operation has two unusual problems: we have to think
about consistency with the methods that already exist (problem for (2)),
and the operation itself is not simple (problem for (1)).
After all, `string_view` already has a substring method — it's called
`substr`!
    Func(str.substr(1, str.size() - 3));
This is shorter than Jason's
    Func(std::string_view(str.begin()+1, str.end()-2)); // ...or...
    Func(std::string_view(str.data()+1, str.size()-3));
but it is equally as mathy and un-ergonomic.

The Best Solution, which C++ will probably never get, is for negative
indices to be treated Python-style, so we could write something like
    Func(str.fromto(1, -2));
But that's still got trouble because "the operation is not simple." Is that
second argument a *position* or a *length*? I tried to indicate "position"
by naming the method `fromto`... but `fromto` is a *terrible* name a-priori.

Basically, no small tweak seems appropriate here. You can't even apply
guideline (2), because what even is the past tense of "remove_prefix"?
    Func(str.removed_prefix(1).removed_suffix(2)); // ??
    Func(str.remove_prefixed(1).remove_suffixed(2)); // ??
    Func(str.after_removing_prefix(1).after_removing_suffix(2)); // ??
The mutators have such unwieldy names that the non-mutators are constrained
(by consistency) to also have unwieldy names and thus probably go unused.

Finally, I note that in C++20 Ranges the `views::take` and `views::drop`
adaptors special-case `string_view`, so all you need is to wait for
`drop_last`. Circa C++26-ish you might be able to finally write
    Func(str | std::views::drop(1) | std::views::drop_last(2));
And then it's relatively easy to write your own "un-piping wrapper" that
would let you use nice object-method syntax with all the standard view
adaptors:
    Func(+UnPipe(str).drop(1).drop_last(2));
In fact, someone's probably done a library like that already, right? If
not, somebody has a niche to fill! :)

my $.02,
Arthur

Received on 2023-01-07 23:13:46