设计函数模板以处理字符串/ char参数的最佳实践是什么?

时间:2018-05-27 06:42:31

标签: c++ c++17

我想写一个简单的字符串split函数。

该函数应该使用一个std::basic_string和一个分隔符(可能是CharTstd::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);
    }
}

我的最终目标是扩展此功能以实现:

  1. 最终结果总是std::basic_string<CharT>放入一些conts
  2. 第一个参数str可以是std::basic_string<CharT>const CharT*或字符串文字。
  3. 第二个参数delimiter可以是charstd::basic_string<CharT> / const CharT* /字符串文字,这意味着分隔符的长度大于1,例如将aaa,,bbb,c,,分开aaa/bbb,c
  4. 第三个参数可以是来自STL的任何序列容器。
  5. 由于人们通常使用C ++处理现代咒语,因此只有std::basic_string<CharT>才能进行简化。

    鉴于函数(模板)可以重载,我想知道

    1. 在这种情况下,我至少需要多少功能?
    2. 设计此类功能的最佳做法是什么(如何编写更多通用功能)?例如,也许要使上述函数使用const CharT*分隔符,行std::size_t len = delimiters.size();必须更改为某些std::distance(...)
    3. 更新

      添加了一份重复代码审核here

3 个答案:

答案 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::stringstd::string_viewconst 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" ");

修改:charchar16_t

添加了重载

修改2

在上面的代码中,split_impl执行实际操作。提供split重载只是为了简化用户代码,因此您不必显式指定要使用的字符类型。没有重载是必要的,因为当参数类型为Char并且您传递的是不同类型的参数(例如,basic_string_view或{{}时,编译器无法推导const char* 1}})。一般来说,我认为这不是一个大问题 - 可能,你想要有四个重载(std::wstringcharchar16_tchar32_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类型)。出于这个原因,我删除了它。此外,此代码假定TextTtext使用相同的字符类型,并且可以转换为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>类型。