我想写一个简单的字符串split
函数。
该函数应该使用一个std::basic_string
和一个分隔符(可能是CharT
或std::basic_string
),并将结果放入ContainerT
。
我的第一次尝试是
template <typename StringT, typename DelimiterT, typename ContainerT>
void split(
const StringT &str, const DelimiterT &delimiters, ContainerT &conts) {
conts.clear();
std::size_t start = 0, end;
std::size_t len = delimiters.size();
while ((end = str.find(delimiters, start)) != StringT::npos) {
if (end - start) {
conts.emplace_back(str, start, end - start);
}
start = end + len;
}
if (start != StringT::npos && start < str.size()) {
conts.emplace_back(str, start, str.size() - start);
}
}
我的最终目标是扩展此功能以实现:
std::basic_string<CharT>
放入一些conts
。str
可以是std::basic_string<CharT>
,const CharT*
或字符串文字。delimiter
可以是char
或std::basic_string<CharT>
/ const CharT*
/字符串文字,这意味着分隔符的长度大于1,例如将aaa,,bbb,c
与,,
分开aaa/bbb,c
。STL
的任何序列容器。由于人们通常使用C ++处理现代咒语,因此只有std::basic_string<CharT>
才能进行简化。
鉴于函数(模板)可以重载,我想知道
const CharT*
分隔符,行std::size_t len = delimiters.size();
必须更改为某些std::distance(...)
?更新
添加了一份重复代码审核here。
答案 0 :(得分:3)
您可以使用std::string_view
来分割文本和分隔符。此外,您可以使用模板模板参数来选择结果中的元素类型:
template<typename Char, template<typename> class Container, typename String>
Container<String> split_impl(std::basic_string_view<Char> text, std::basic_string_view<Char> delim)
{
Container<String> result;
//...
result.push_back(String(text.substr(start, count)));
//...
return result;
}
template<template<typename> class Container, typename String = std::string_view>
Container<String> split(std::string_view text, std::string_view delim)
{ return split_impl<char, Container, String>(text, delim); }
template<template<typename> class Container, typename String = std::u16string_view>
Container<String> split(std::u16string_view text, std::u16string_view delim)
{ return split_impl<char16_t, Container, String>(text, delim); }
这样,它可以与std::string
,std::string_view
和const char*
一起使用而无需冗余分配:
// vector of std::string_view objects:
auto words_1 = split<std::vector>("hello world", " ");
// list of std::string objects:
auto words_2 = split<std::list, std::string>(std::string("hello world"), " ");
// vector of std::u16string_view objects:
auto words_3 = split<std::vector>(u"hello world", u" ");
修改:为char
和char16_t
修改2
在上面的代码中,split_impl
执行实际操作。提供split
重载只是为了简化用户代码,因此您不必显式指定要使用的字符类型。没有重载是必要的,因为当参数类型为Char
并且您传递的是不同类型的参数(例如,basic_string_view
或{{}时,编译器无法推导const char*
1}})。一般来说,我认为这不是一个大问题 - 可能,你想要有四个重载(std::wstring
,char
,char16_t
,char32_t
),如果不是更少
但是,为了完整性,这里有一个不使用重载的替代方法:
wchar_t
使用此方法,您无法使用template<typename ContainerT, typename TextT, typename DelimT>
ContainerT split(const TextT& text, const DelimT& delim)
{
using CharT = std::remove_reference_t<decltype(text[0])>;
std::basic_string_view<CharT> textView(text);
std::basic_string_view<CharT> delimView(delim);
ContainerT result;
// actual implementation, but using textView and delimView instead of text and delim
result.push_back(textView.substr(start, count));
return result;
}
// usage:
auto words = split<std::vector<std::string_view>>("some text", " ");
模板参数的默认值,如上所述(因为它必须依赖于String
类型)。出于这个原因,我删除了它。此外,此代码假定TextT
和text
使用相同的字符类型,并且可以转换为delim
。
就个人而言,我更喜欢版本1.它不使用模板类型的函数参数,这是恕我直言的更好,因为它让调用者更好地了解应该传入什么。换句话说,第一个{{1的接口更好地指定。另外,如上所述,我不认为必须添加四个basic_string_view
问题的重载。
答案 1 :(得分:1)
从输入字符串构造basic_string_view
,然后对它们进行操作。 basic_string_view
有一个显式构造函数为char*
,basic_string
有一个强制转换操作符basic_string_view
。
答案 2 :(得分:0)
我的建议是使用两个模板参数,一个用于输入字符串,一个用于输出容器,因为在几乎所有情况下,输入字符串,分隔符和输出容器将是相同类型,因此您可以定义您的函数,如下所示 -
template<typename charT, typename Container)
void split(const std::basic_string<charT> input,
const charT deliminator,
Container<std::basic_string<chart>> &cont)
对于第二种情况,您的分隔符可以是std::basic_string<charT>
类型。