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

C++ 设计函数模板来处理字符串/字符参数的最佳实践是什么?,c++,c++17,C++,C++17,我想写一个简单的字符串split函数 该函数应使用一个std::basic_字符串和一个分隔符(可能是图表或std::basic_字符串),并将结果放入容器中 我的第一次尝试是 template <typename StringT, typename DelimiterT, typename ContainerT> void split( const StringT &str, const DelimiterT &delimiters, ContainerT

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

该函数应使用一个
std::basic_字符串
和一个分隔符(可能是
图表
std::basic_字符串
),并将结果放入
容器中

我的第一次尝试是

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);
    }
}
模板
空分(
常量StringT&str、常量分隔符rt&delimiters、ContainerT&conts){
继续清除();
标准::大小\u t开始=0,结束;
std::size\u t len=分隔符.size();
while((end=str.find(delimiters,start))!=StringT::npos){
如果(结束-开始){
续:后置(str、start、end-start);
}
开始=结束+长度;
}
if(start!=StringT::npos&&start
我的最终目标是扩展此功能以实现:

  • 最终结果总是
    std::basic_string
    放入一些
    conts
  • 第一个参数
    str
    可以是
    std::basic_string
    const CharT*
    或字符串文字
  • 第二个参数
    分隔符
    可以是
    字符
    ,也可以是
    std::basic_string
    /
    const CharT*
    /string literal,这意味着分隔符的长度大于1,例如拆分
    aaa,,bbb,c
    给出
    aaa/bbb,c
  • 第三个参数可以是
    STL
    中的任何序列容器
  • 因为通常在C++中处理现代STOP,5月2日是<代码> STD::Basic字符串只用于简化。 考虑到函数(模板)可以重载,我想知道

  • 在这种情况下,我至少需要多少函数
  • 设计此类函数的最佳实践是什么(如何编写更通用的函数)?例如,要使上述函数与
    const-CharT*
    分隔符一起工作,行
    std::size\u t len=delimiters.size()必须更改为某些
    标准::距离(…)
  • 更新:


    添加了一个可撤销代码审查。

    从输入字符串构造
    基本字符串视图
    ,然后对这些字符串进行操作。
    basic\u string\u视图
    有一个采用
    char*
    的显式构造函数,
    basic\u string
    有一个对
    basic\u string\u视图
    的强制转换操作符,我的建议是使用两个模板参数,一个用于输入字符串,一个用于输出容器,因为在几乎所有情况下,输入字符串,除味器和输出容器将是相同的类型,所以您可以像这样定义您的函数-

    template<typename charT, typename Container)
    void split(const std::basic_string<charT> input,
        const charT deliminator,
        Container<std::basic_string<chart>> &cont)
    

    template您可以使用
    std::string\u视图
    对要拆分的文本和delimeter使用。此外,还可以使用“模板”参数选择结果中的图元类型:

    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); }
    
    编辑:
    char
    char16\t

    编辑2

    在上面的代码中,
    split\u impl
    执行实际工作<提供代码>拆分
    重载只是为了简化用户代码,因此您不必显式指定要使用的字符类型。如果没有重载,这是必要的,因为当参数的类型为
    basic\u string\u view
    并且您正在传递不同类型的参数(例如,
    const Char*
    std::wstring
    )时,编译器无法推断
    Char
    。一般来说,我认为这不是一个大问题-可能,您希望有四个重载(
    char
    char16\u t
    char32\u t
    wchar\u 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", " ");
    
    模板
    ContainerT拆分(常量文本和文本、常量删除和删除)
    {
    使用CharT=std::删除\u参考\u t;
    标准::基本字符串视图文本视图(文本);
    std::基本字符串视图delimView(delim);
    ContainerT结果;
    //实际实现,但使用textView和delimView而不是text和delim
    结果。推回(textView.substr(开始,计数));
    返回结果;
    }
    //用法:
    自动单词=拆分(“某些文本”,“拆分”);
    
    使用这种方法,您不能像上面那样使用
    String
    template参数的默认值(因为它必须依赖于
    TextT
    type)。出于这个原因,我将其删除。此外,此代码假定
    text
    delim
    使用相同的字符类型,并且可以转换为
    基本字符串视图


    就个人而言,我更喜欢版本1。它不使用模板类型作为函数参数,这是更好的,因为它让调用者更好地了解应该传递什么。换句话说,最好指定第一个
    拆分的接口。此外,正如上面提到的,我不认为必须添加四个重载的<代码>分割<代码>一个问题。

    我发现这是棘手的,因为所有潜在的组合。我尝试过很多方法。我当前使用的一个主模板函数只对输入字符串和分隔符使用迭代器(四个迭代器s_begin、s_end、d_begin、d_end)。然后我有很多不同的重载,它们将输入参数转换成最基本的形式。您看到了关于是否应该使用一些
    std::basic_string_view
    作为模板函数参数的顶部答案了吗?如果传递一个
    std::string
    它似乎不能被隐式转换,这在传递参数时似乎失去了一些便利。
    std::string
    有一个转换操作符用于转换到
    std::string\u视图
    ,所以这应该不是问题。谢谢您的回答。此版本似乎不适合
    wstring/u16string/u32string
    。虽然
    string
    可以隐式转换为
    string\u视图
    basic\u string
    似乎无法转换为
    basic\u string_
    
    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", " ");