bolov
bolov

Reputation: 75688

Erase inside a std::string by std::string_view

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?

Upvotes: 0

Views: 1039

Answers (2)

eerorika
eerorika

Reputation: 238311

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);
}

Upvotes: 1

bolov
bolov

Reputation: 75688

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.

Upvotes: 1

Related Questions