std::basic_string::trim
Суть
Добавить в шаблон класса строки методы 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));
}
Я бы оставил пользователю возможность передавать свой предикат для этих функций
Или хотя бы массив символов (но для этого придется делать эту мембер функцию шаблонной, что приведет к 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);
Немного неясны несколько моментов:
- Почему
trim[_if]должны возвращатьsize_type? Логичнее возвращать ссылку на себя, как в моем оригинальном предложении. - Зачем передавать вариадик-пак чаров? Есть уже замечательные функции
constexpr size_type find_first_of( const CharT* s, size_type pos = 0 ) const;
constexpr size_type find_first_not_of( const CharT* s, size_type pos = 0 ) const;
...
Суть параметра s ровно такая же. Да и как-то уж больно странно будет вот так использовать функцию:
str.trim(' ', '\t', '\n');
В итоге то никаких темплейтных мемберов и не получается (что хорошо):
constexpr basic_string& trim();
constexpr basic_string& trim(const char_type* s);
// trim_left и trim_right аналогично
Почему 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
Делал по аналогии с 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 ifstd::is_convertible_v<const T&, std::basic_string_view<CharT, Traits>>istrueandstd::is_convertible_v<const T&, const CharT*>isfalse.
Это что касается basic_string_view, далее имеет смысл добавить перегрузки для char_type и basic_string.