0

I need to find and then erase a portion of a string (a substring). string_view seems such a good idea, but I cannot make it work with string::erase:

// guaranteed to return a view into `str`
auto gimme_gimme_gimme(const std::string& str) -> std::string_view;

auto after_midnight(std::string& str)
{
    auto man = gimme_gimme_gimme(str);

    str.erase(man); // way to hopeful, not a chance though
    str.erase(man.begin(), man.end()); // nope
    str.erase(std::distance(str.begin(), man.begin()), man.size()); // nope
    str.erase(std::distance(str.data(), man.data()), man.size()); // nope again

    // for real???
}

Am I overthinking this? Given a std::string_view into a std::string how to erase that part of the string? Or am I misusing string_view?

bolov
  • 72,283
  • 15
  • 145
  • 224
  • I wouldn't use a string_view here. I would either return a `pair` where `first` is the index of the substring and `second` is the length, or just modify `gimme_gimme_gimme` to actually do the erasure. – NathanOliver May 06 '20 at 15:48
  • When you say "erase portion of string" what do you mean? If string is "abbabca", and string view is "ab", then what do you want erased exactly? – eerorika May 06 '20 at 15:51
  • @eerorika a substring. In your example if I understand the view is into the first two characters of the string, erasing that gives `"ba"` – bolov May 06 '20 at 15:53
  • @bolov I edited my comment – eerorika May 06 '20 at 15:54
  • @eerorika oh I see. I mean a substring of the original string. The `string_view` is a view into the original string, it does not point to another object. – bolov May 06 '20 at 15:55
  • @bolov Oh, I see. – eerorika May 06 '20 at 15:57
  • So, essentially the issue is that `std::distance` does not work with mismatched iterator and const_iterator pairs (which in this case are pointers to char and const char respectively). This is indeed an inconvenience. It's fairly reasonable that it won't work with mismatched sring_view, string iterator combo. – eerorika May 06 '20 at 16:04
  • @NathanOliver why? It's because working with `string_view` is cumbersome? I can understand that and that means the std library needs to add APIs to work with it. Or is it because `string_view` is not meant to be used this way. And in this case I would argue that the value I personally see in `span` and `string_view` is the abstraction of a "range", going away from two separate iterator parameters to specify a range. `std::pair` can mean `(index_begin, index_end)` or `(index_begin, length)`. I liked the idea of a class that expresses this intend of a range clearly. – bolov May 06 '20 at 16:05
  • @eerorika I see the very fact that we need to resort to `std::distance` an inconvenience. Ideally `str.erase(man);` should work. But since the entire std is based around `(being, end)` arguments `str.erase(man.begin(), man.end());` should have worked imho. – bolov May 06 '20 at 16:07
  • Yeah, my reasons is just that string view isn't built for this, and as your answer shows to make it work the code gets "ewwy". – NathanOliver May 06 '20 at 16:07
  • @NathanOliver so basically we still don't have a way to work elegantly with contiguous portions or arrays/strings. Or maybe the the use case I see just isn't there... – bolov May 06 '20 at 16:11
  • @bolov I suspect that the reason why such erase overload is not in the standard is concisely expressed in your answer: "The code is error prone". Technically, it's not any more error prone that erasing iterators, but the issue is that the precondition that iterator arguments are to the same container are conventional, while precondition that string view is to the same container is non-conventional. – eerorika May 06 '20 at 16:11
  • @eerorika that's a good point. – bolov May 06 '20 at 16:12

2 Answers2

1

Am I overthinking this?

You're under thinking it, unless I'm missing something obvious. To make the code compile you need this:

auto gimme_gimme_gimme(const std::string& str) -> std::string_view;

auto after_midnight(std::string& str)
{
    auto man = gimme_gimme_gimme(str);

    str.erase(std::distance(std::as_const(str).data(), man.data()), man.size()); // urrr... growling in pain
}

But wait!! There's more! Notice I said "to make it compile". The code is error prone!! Because...

std::string::data cannot be nullptr but an empty string_view can be represented as (valid pointer inside the string + size 0) or as (nullptr + size 0). The problem arises if the string_view::data is nulltpr because of the std::distance used.

So you need to make sure that the string_view always points inside the string, even if the view is empty. Or do extra checks on the erase side.

bolov
  • 72,283
  • 15
  • 145
  • 224
1

The string view could indeed be empty, or it could be a view to the outside of the container. Your suggested erase overload, as well as the implementation of the function in your answer relies on a pre-condition that the string view is to the same string object.

Of course, the iterator overloads are very much analogous and rely on the same pre-condition. But such pre-condition is conventional for iterators, but non-conventional for string views.

I don't think that string view is an ideal way to represent the sub range in this case. Instead, I would suggest using a relative sub range based on the indices. For example:

struct sub_range {
    size_t begin;
    size_t count;
    constexpr size_t past_end() noexcept {
        return begin + count;
    }
};

It is a matter of taste whether to use end (i.e. past_end) or count for the second member, and to provide the other as a function. Regardless, there should be no confusion because the member will have a name. Using count is somewhat more conventional with indices.

Another choice is whether to use signed or unsigned indices. Signed indices can be used to represent backwards ranges. std::string interface doesn't understand such ranges however.

Example usage:

auto gimme_gimme_gimme(const std::string& str) -> sub_range;

auto after_midnight(std::string& str)
{
    auto man = gimme_gimme_gimme(str);
    str.erase(man.begin, man.distance);
}
eerorika
  • 232,697
  • 12
  • 197
  • 326