能否编写一个函数同时适用于 std::string 和 std::wstring?

10

我刚刚为std :: string编写了一个简单的实用程序函数。然后我注意到,如果std :: stringstd :: wstringstd :: u32string,该函数看起来完全相同。在这里是否可以使用模板函数?我对模板不是很熟悉,并且std :: stringstd :: wstring本身就是模板,这可能会成为问题。

template<class StdStringClass>
inline void removeOuterWhitespace(StdStringClass & strInOut)
{
  const unsigned int uiBegin = strInOut.find_first_not_of(" \t\n");

  if (uiBegin == StdStringClass::npos)
  {
    // the whole string is whitespace
    strInOut.clear();
    return;
  }

  const unsigned int uiEnd   = strInOut.find_last_not_of(" \t\n");
  strInOut = strInOut.substr(uiBegin, uiEnd - uiBegin + 1);
}

这是一种正确的做法吗?在使用StdStringClass模板类并调用通常的std::string函数,如find、 replace、erase等时,是否存在问题?我指的不是这个函数,而是使用模板类的一般概念。


1
我没有看到什么特别的问题。对我来说,我们的模板看起来很好。使用一些特定的函数也没有问题。只是如果你给模板内部没有使用过的函数传递参数,它就无法编译。 - Garf365
1
“查找”和“替换”需要一些技巧,因为字符类型不同。例如,上面的函数对于std::wstring将无法工作,因为std::wstring::find_first_not_of接受的是const wchar_t*而不是const char* - Benjamin Lindley
1
对我来说看起来很好,除了使用 unsigned int,我建议使用 typename StdStringClass::size_type 或者如果启用了 C++11,则使用 auto - Radek
@Benjamin 或许应该将搜索的字符串" \t\n"包装到StdStringClass(" \t\n")中,这样可以解决问题吗? - Radek
1
@Radek:不行,因为这会尝试调用 wstring 不存在的构造函数。 - Benjamin Lindley
这个函数的正确名称应该是 trim - Orwellophile
2个回答

6

这个想法不错,但我会选择在std::basic_string的基础上构建模板,而不是一般的StdStringclass

template<class T>
inline void removeOuterWhitespace(std::basic_string<T>& strInOut)
{
  constexpr auto delim[] = {T(' '),T('\t'),T('\n'),T(0)};
  const auto uiBegin = strInOut.find_first_not_of(delim);

  if (uiBegin == std::basic_string<T>::npos)
  {
    // the whole string is whitespace
    strInOut.clear();
    return;
  }

  const auto  uiEnd   = strInOut.find_last_not_of(delim);
  strInOut = strInOut.substr(uiBegin, uiEnd - uiBegin + 1);
}

我建议在favro中放弃类似MSDN风格的“inout”标注,改用更简单的名称,比如str。程序员会自行猜测str是结果,因为它作为非const引用传递,函数返回void
同时,我将unsigned int改为auto。所有标准C++容器/字符串在返回索引时都返回size_tsize_t可能不是unsigned intauto会匹配正确的返回值类型。

我明白了。非常好的答案。不幸的是,C++11对我不可用。我希望能够规避“auto”。一段时间前,当我使用“size_t”时,gcc编译器发出了警告,因此我切换到了“unsigned int”。 - Fabian
1
你仍然可以使用C++03来完成它。而不是使用“constexpr auto...”,只需编写“const T”和“std::basic_string<T>::size_type”即可完成其余部分。 - David Haim
3
我建议不要直接使用basic_string,有一个非常好的原因:如果我以后切换到提供我在此处使用的接口的其他字符串类,该怎么办?我会使用typename StringType并使用StringType::value_type来获取其底层字符类型。话虽如此,最好使用某种形式的isspace谓词而不是硬编码的空白字符列表。特别是如果使用wstring(暗示着不仅仅是ASCII空格字符)。 - rubenvb
@rubenvb:感谢您的评论。这两点都是我会考虑的重要因素。 - Fabian
如果您的模板基于 basic_string,那么最好将所有三个(字符类型、特征和分配器)都进行模板化,而不仅仅是其中一个。 - T.C.
显示剩余2条评论

-1
假设您的模板按预期工作(抱歉,我没有检查),另一个选项是将函数包装在类中,并使用构造函数控制要应用该函数的字符串类型类。

编辑:添加了说明性框架

EDIT2 编译通过的代码(至少在VS2015上):-)

class StringType1;
class StringTypeN;

class str {

    //template function
    template<class StdStringClass>
    inline void removeOuterWhitespace(StdStringClass & strInOut)
    {
        //.
        //.
        //.
    }

public:
    //constructors
    str(StringType1 &s1) { removeOuterWhitespace(s1); }
    //.
    //.
    //.
    str(StringTypeN &sN) { removeOuterWhitespace(sN); }


};

int main() {

    return 0;
}

编辑3 概念验证

#include <iostream>
class incr {
    //template function
    template<class incrementor>
    inline void removeOuterWhitespace(incrementor & n)
    {
        n++;
    }
public:
    //constructors
    incr(int &n1) { removeOuterWhitespace(n1); }
    incr(double &n1) { removeOuterWhitespace(n1); }
    incr(float &n1) { removeOuterWhitespace(n1); }
};

int main() {
    int n1 = 1;
    double n2 = 2;
    float n3 = 3;
    std::cout << n1 << "\t" << n2 << "\t" << n3 << std::endl;
    auto test1 = incr(n1);
    auto test2 = incr(n2);
    auto test3 = incr(n3);
    //all variables modified
    std::cout << "all variables modified by constructing incr" << std::endl;
    std::cout << n1 << "\t" << n2 << "\t" << n3 << std::endl;
    return 0;
}

请进一步阐述并添加一些代码以说明您的答案。目前这不是一个有帮助的答案,因为它没有为OP的问题提供一个明确的解决方案。 - Revolver_Ocelot
假设我理解你的意思,我不明白这会如何减少代码重复。在类中,我仍然需要为每种类型的字符串实现上述代码。我的目标是坚持DRY原则。对我来说,使用一个模板方法更加优雅。 - Fabian
Fabian,@Revolver_Ocelot,添加了概念证明。正如我所说,这种方法增加了控制,如果您尝试传递未设计模板例程的值,您的IDE/编译器将会报错。干杯! - Adl A
它没有回答关于潜在陷阱和实现通用函数的正确方式的OP问题。事实上,它有很多问题:(a)你需要手动指定可以使用的所有类。任何满足函数要求但未列出的类都不能使用。(b)您可以创建一个无用的类实例,甚至不能用作谓词。(c)它比通过SFINAE检查要求并通过函数重载禁用函数更复杂且不够通用。即使没有SFINAE,通过调用模板添加对函数的控制也更容易。 - Revolver_Ocelot
@aRevolver_Ocelot a) 这就是整个重点 b) 是的,但只要稍加想象力就可以轻松解决。换句话说,这取决于实现方式 c) 对此不太确定。无论如何,在使用C++进行各种操作的众多方法中,这只是另一种方法,OP - 或任何其他人都可以将其添加到自己的工具库中,如果愿意,也可以完全忽略它。干杯! :) - Adl A
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接