如何在不重复代码的情况下模板化函数,只更改解析函数?

时间:2014-08-06 20:02:05

标签: c++ templates

我有一个现有的函数,它将逗号分隔的数字串转换为一个向量,例如" 1,2,3"变成[1,2,3]

该功能看起来非常像:

bool ConvertStringToNumberList(string input, vector<int32_t>& output)
{
  <bunch of code>
  int32_t value = strtol(str, 0, /*base*/ 10);
  <bunch of code>
}

我想将其更改为适用于int32_t,uint32_t,double和float的模板函数。

问题是,对于每种数据类型,都有不同的解析函数(例如strtol,strtoul,strtod,strtof)可能需要不同数量的参数(例如strtod()不需要&#34; base& #34;参数)。

如何在不重复<bunch of code>但仅更改解析函数的情况下模板化上述代码?

5 个答案:

答案 0 :(得分:4)

只需创建具有特化的类,每个类都有一个静态函数来解析您想要支持的类型之一并从函数模板中使用它。

E.g:

template <typename T>
struct Converter {};

template <>
struct Converter<int32_t> {
   static int32_t Parse(const std::string& str) {
      return strtol(str, 0, /*base*/ 10); 
   }
};

template <>
struct Converter<double> {
   static double Parse(const std::string& str) {
      return atof(str); 
   }
};

template <typename T>
bool ConvertStringToNumberList(string input, vector<T>& output) {
  // <bunch of code>
  T value = Converter<T>::Parse(str);
  // <bunch of code>
}

或者,你可能有普通的Parse函数,它对每种类型都有重载, 但在那里你可能会得到意想不到的类型促销。

答案 1 :(得分:1)

除非你真的想自己完成整个工作,否则最好使用strtol之外的其他东西开始。这里非常适合的一种可能性是boost::lexical_cast

template <class T>
ConvertStringToNumberList(string input, vector<T> &output) { 
    <bunch of code>
    T value = boost::lexical_cast<T>(str);
    <bunch more code>
}

lexical_cast几乎等同于将字符串填充到字符串流中,然后读出指定的类型,但它提供了重载以优化许多最常见的类型,因此在许多(大多数?)典型情况下,它的速度要快得多。

答案 2 :(得分:1)

您可以通过将其掩码表值设置为std::ctype_base::space来添加用于流提取的自定义分隔符,使用该表创建构面,使用构面创建区域设置,在流上填充该区域设置并使用{{1或} operator>>来提取你的东西。

你需要一些标题......

std::istream_iterator

方面看起来像这样:

#include <vector>
#include <locale>
#include <iostream>
#include <sstream>
#include <iterator>
#include <cstring> 

它可以用于创建一个带有此方面的元素向量的单个临时函数:

class custom_delimter_facet : public std::ctype<char>
{
public:
  static custom_delimter_facet * new_facet(std::string const &delimeters)
  {
    std::ctype<char>::mask * mask_table 
      = new std::ctype<char>::mask[std::ctype<char>::table_size];
    // Set zero
    std::memset(mask_table, 0, 
            sizeof(std::ctype<char>::mask)*std::ctype<char>::table_size);
    // Set delimeters
    for (auto delim : delimeters)
      mask_table[delim] = std::ctype_base::space;
    // create facet with table
    return new custom_delimter_facet(mask_table);
  }
  custom_delimter_facet(std::ctype<char>::mask const * tbl)
    : std::ctype<char>(tbl, false), m_facet_mask(tbl)
  { }
  ~custom_delimter_facet()
  {
    if (m_facet_mask) delete[] m_facet_mask;
  }
private:
  std::ctype<char>::mask const * m_facet_mask;
};

小测试:

template<typename T>
std::vector<T> vec_from_string(std::string const &string, 
  std::string const & delimeters = " \n")
{
  // Create input stringstream
  std::istringstream siss(string);
  // Imbue custom delimeted locale
  // facet will be deleted by locale destructor
  siss.imbue(std::locale(siss.getloc(), 
    custom_delimter_facet::new_facet(delimeters)));
  // Fill vector
  std::vector<T> data;
  std::copy(std::istream_iterator<T>(siss),
    std::istream_iterator<T>(),
    std::back_inserter(data));
  // return vector
  return data;
}

我期望的结果是:

Result 1 size: 8
Result 2 size: 7
Result 3 size: 3
Res1 item: 1423
Res1 item: 1232
Res1 item: 545367
Res1 item: 123231
Res1 item: 123454
Res1 item: 353456
Res1 item: 524
Res1 item: 24234
Res2 item: 17
Res2 item: 23
Res2 item: 55
Res2 item: 44
Res2 item: 63
Res2 item: 57
Res2 item: 0
Res3 item: 13.4472
Res3 item: 29.2247
Res3 item: 44.6

答案 3 :(得分:1)

boost:lexical_cast外,您还可以选择使用std::istringstream。这将使您可以选择转换失败,无论是返回错误还是抛出异常。就像boost:lexical_cast一样,将其用于现有代码所需的代码量是最小的,并且不需要任何额外的函数或重载来处理实际的转换。

在下面的示例中,可以通过检查operator>>的返回类型来确定转换是否成功。默认情况下,字符串流返回错误而不是抛出异常,因此不需要其他代码。这将非常适合您现有的代码。

template<class ValueType>
bool ConvertStringToNumberList(string input, vector<ValueType>& output)
{
    // <bunch of code>

    ValueType value;
    if (!(istringstream(input) >> value))
    {
        //  Conversion faild
        return false;
    }

    // <bunch of code>
    return true;
}

以下示例指定转换失败时抛出的异常。在这种情况下,除非数据预计是正确的,否则我不认为需要例外。

template<class ValueType>
bool ConvertStringToNumberList2(string input, vector<ValueType>& output)
{
    // <bunch of code>

    ValueType value;
    istringstream stream(input);
    stream.exceptions(istringstream::failbit);
    stream >> value;

    // <bunch of code>
    return true;
}

两个示例都将input的上下文传递给字符串流,以便您更轻松地进行测试。只需将input更改为保存从输入字符串中提取的文本的任何变量。

答案 4 :(得分:0)

除了模板化你的功能外,还要创建一个新的功能来处理代码行

int32_t value = strtol(str, 0, /*base*/ 10);

此函数不需要是模板 - 只需使用重载并为您需要的每种数据类型创建适当的转换函数重载。然后从模板中调用重载函数 - 将自动选择特定于该类型的函数。