cpp-ru / ideas

Идеи по улучшению языка C++ для обсуждения
https://cpp-ru.github.io/proposals
Creative Commons Zero v1.0 Universal
88 stars 0 forks source link

std::basic_string::trim #452

Open GeorgiiFirsov opened 3 years ago

GeorgiiFirsov commented 3 years ago

Суть

Добавить в шаблон класса строки методы trim_left, trim_right и trim, которые обрезают пробельные символы слева, справа или по обеим сторонам строчки.

Применение

Полезно, к примеру, при построчном чтении файлов, при обработке ввода от пользователя и тому подобных случаях. Часто на моей практике встречаются ситуации, когда пробельные символы по сторонам строчки необходимо убрать.

Пример реализации

Возможная реализация

constexpr basic_string& trim_left()
{
    const auto is_not_space = std::not_fn(static_cast<int(*)(int)>(std::isspace));
    this->erase(cbegin(), std::find_if(cbegin(), cend(), is_not_space));
    return *this;
}

constexpr basic_string& trim_right()
{
    const auto is_not_space = std::not_fn(static_cast<int(*)(int)>(std::isspace));
    this->erase(std::find_if(crbegin(), crend(), is_not_space).base(), cend());
    return *this;
}

constexpr basic_string& trim()
{
    return trim_left().trim_right();
}

Как можно сейчас

// C++17 и новее

template<typename Char, typename Traits, typename Allocator>
constexpr basic_string<Char, Traits, Allocator>& trim_left(basic_string<Char, Traits, Allocator>& s)
{
    const auto is_not_space = std::not_fn(static_cast<int(*)(int)>(std::isspace));
    s.erase(s.cbegin(), std::find_if(s.cbegin(), s.cend(), is_not_space));
    return s;
}

template<typename Char, typename Traits, typename Allocator>
constexpr basic_string<Char, Traits, Allocator>& trim_right(basic_string<Char, Traits, Allocator>& s)
{
    const auto is_not_space = std::not_fn(static_cast<int(*)(int)>(std::isspace));
    s.erase(std::find_if(s.crbegin(), s.crend(), is_not_space).base(), s.cend());
    return s;
}

template<typename Char, typename Traits, typename Allocator>
constexpr basic_string<Char, Traits, Allocator>& trim(basic_string<Char, Traits, Allocator>& s)
{
    return trim_left(trim_right(s));
}
unterumarmung commented 3 years ago

Я бы оставил пользователю возможность передавать свой предикат для этих функций

Или хотя бы массив символов (но для этого придется делать эту мембер функцию шаблонной, что приведет к str.template trim(chars...)), как в других языках: C#: https://docs.microsoft.com/en-us/dotnet/api/system.string.trim?view=net-5.0#overloads Python: https://docs.python.org/3.4/library/stdtypes.html?highlight=strip#str.lstrip

Возможно, стоит сделать по аналогии с erase и erase_if, что-то типа:

template <typename Char, typename Traits, typename Allocator, std::predicate<Char> Predicate>
typename std::basic_string<Char, Traits, Allocator>::size_type trim_if(std::basic_string<Char, Traits, Allocator>& str, Predicate&& predicate);

template <typename Char, typename Traits, typename Allocator, typename... Chars>
    requires std::convertible_to<Chars, Char> && ...
typename std::basic_string<Char, Traits, Allocator>::size_type trim(std::basic_string<Char, Traits, Allocator>& str, Chars&&... chars);
GeorgiiFirsov commented 3 years ago

Немного неясны несколько моментов:

В итоге то никаких темплейтных мемберов и не получается (что хорошо):

constexpr basic_string& trim();
constexpr basic_string& trim(const char_type* s);

// trim_left и trim_right аналогично
unterumarmung commented 3 years ago

Почему trim[_if] должны возвращать size_type? Логичнее возвращать ссылку на себя, как в моем оригинальном предложении.

Делал по аналогии с erase[_if]: функция возвращает количество удаленных элементов

constexpr basic_string& trim(const char_type* s);

Тогда уж лучше принимать std::basic_string_view: чтобы перенести бремя UB с отсутствием '\0' или вообще s == nullptr на пользователя.

В итоге то никаких темплейтных мемберов и не получается (что хорошо)

Считаю, что trim_if (и по аналогии другие тоже) должны всё-таки существовать, и их придётся делать шаблонами.

И ещё предлагаю не забывать про аналогичные функции для `str::basic_string_view

GeorgiiFirsov commented 3 years ago

Делал по аналогии с erase[_if]: функция возвращает количество удаленных элементов

Есть нюанс. Тот же erase не предполагает использования её результата прямо на месте, а вот trim выглядит очень органично в таком контексте:

std::string some_input = ...;
ProcessInput(some_input.trim());

trim надо скорее сопоставлять с substr (чем в сущности он и является).

Тогда уж лучше принимать std::basic_string_view

Лучше для консистентности тут поступить тогда, как в уже упомянутых find_first_of и иже с ней:

constexpr size_type find_first_of( const CharT* s, size_type pos = 0 ) const;

template < class T >
constexpr size_type find_first_of( const T& t, size_type pos = 0 ) const noexcept(/* see below */);

При этом последняя имеет следующее описание:

Implicitly converts t to a string view sv as if by std::basic_string_view<CharT, Traits> sv = t;, then finds the first character equal to one of the characters in sv. This overload participates in overload resolution only if std::is_convertible_v<const T&, std::basic_string_view<CharT, Traits>> is true and std::is_convertible_v<const T&, const CharT*> is false.

Это что касается basic_string_view, далее имеет смысл добавить перегрузки для char_type и basic_string.