繁体   English   中英

将C ++字符串解析为元组

[英]Parsing a C++ string into a tuple

我正在开发一个简单的CSV解析器,它将文件的行存储在元组中。 如果不是因为文件内的行上的条目数是变量以及它们的类型,这将是一件容易的事。 因此,线条可能是这样的:

1,2.2,你好,18,世界

解析器应该能够像这样工作:

ifstream file("input.csv");
SimpleCSVParser<int, float, string, int, string> parser(file);

当我尝试实现一个解析实际行的函数时,事情就变得复杂了。 我还没有找到一种方法从参数列表中提取下一个类型,以便在调用file >> var之前声明该变量。 我还需要在循环中执行此操作,以某种方式从每次迭代的结果构造元组。

那么如何使用普通的C ++ 11将字符串解析为元组? 我试过这个:

template <typename ...Targs>
tuple<Targs...> SimpleCSVParser<Targs...>::iterator::operator*() {
    istringstream in(cur);
    in.imbue(locale(locale(), new commasep)); // for comma separation
    tuple<Targs...> t;
    for (size_t i = 0; i < sizeof...(Targs); ++i) {
        tuple_element<i,decltype(t)>::type first;
        in >> first;
        auto newt = make_tuple(first);
        // what do I do here?
    }
}

但它不起作用,因为我用来提取类型的元组是空的。

看来,你试图迭代不起作用的元组索引/类型,我想。 但是,你可以做的就是为每个成员调用一个读函数。 我们的想法是将元组的处理委托给一个函数,该函数使用参数包将操作扩展到每个元素上的操作。 std::index_sequence<...>可用于获取整数序列。

像这样的东西:

template <typename T>
bool read_tuple_element(std::istream& in, T& value) {
    in >> value;
    return true;
}

template <typename Tuple, std::size_t... I>
void read_tuple_elements(std::istream& in, Tuple& value, std::index_sequence<I...>) {
    std::initializer_list<bool>{ read_tuple_element(in, std::get<I>(value))... });
}

template <typename ...Targs>
tuple<Targs...> SimpleCSVParser<Targs...>::iterator::operator*() {
    std::istringstream in(cur);
    in.imbue(std::locale(std::locale(), new commasep)); // for comma separation
    std::tuple<Targs...> t;
    read_tuple_elements(in, t, std::make_index_sequence<sizeof...(Targs)>{});
    if (in) { // you may want to check if all data was consumed by adding && in.eof()
        // now do something with the filled t;
    }
    else {
        // the value could *not* successfully be read: somehow deal with that
    }
}

上面代码的基本思想就是创建一个合适的read_tuple_element()调用序列。 在跳转到通用代码之前,假设我们只想用三个元素来实现std::tuple<T0, T1, T2> value读取。 我们可以使用(使用实现所读取的rte()代替read_tuple_element()为了简洁):

rte(get<0>(value)), rte(get<1>(value)), rte(get<2>(value));

现在,不是为每个元素编写这个,如果我们有一个索引序列std::size_t... I可以使用这个序列[几乎]使用

rte(get<I>(value))...;

但是,不允许像这样扩展参数包。 相反,参数包需要放在某个上下文中。 上面的代码为此目的使用std::initializer_list<bool>std::initializer_list<T>的元素按列出的顺序构造。 也就是说,我们得到了

std::initializer_list<bool>{ rte(get<I>(value))... };

缺少的是如何创建参数包I评估一系列合适的索引。 方便的是,标准库定义了std::make_index_sequence<Size> ,它创建了一个std::index_sequence<I...> ,其中I的值为0, 1, 2, ..., Size-1 因此,使用std::make_index_sequence<sizeof...(Targs){}调用read_tuple_elements()会创建一个具有合适的参数列表的对象,这些参数可以推导出来,然后用于将元组扩展为传递给read_tuple_element()的元素序列read_tuple_element()

你不能使用这样的元组。

如果不是因为文件内的行上的条目数是变量以及它们的类型,这将是一件容易的事。

如果我理解,您在处理文件时只能在运行时知道您想要的元组大小和类型。 不幸的是,这必须在编译时知道......

如果您确实想使用元组,则必须对文件进行预处理以确定数据大小和类型。 然后你可以相应地使用正确的元组。 但你不能直接这样做。

for (size_t i = 0; i < sizeof...(Targs); ++i) {
    tuple_element<i,decltype(t)>::type first;
    in >> first;
    auto newt = make_tuple(first);
    // what do I do here?
}

这是运行时。 您应该考虑使用可变参数模板使用递归函数。 那将是编译时间。

通常的方法,通过做这样的事情类型擦除使用,例如union所有可能的值类型的加指示标志,它是实际进入

namespace generic_type {
  struct generic
  {
    enum type { Void=0, Bool=1, Int=2, String=3, Float=4 };
    type Type=Void;
    union {
      bool B;
      std::int64_t I;
      std::string S;
      double X;
    }
    generic() = default;
    generic(generic&&) = default;
    generic(generic const&) = default;
    generic(bool b) : Type(Bool), B(b) {}
    generic(std::int64_t i) : Type(Int), I(i) {}
    generic(std::uint64_t i) : Type(Int), I(i) {}
    generic(std::string const&s) : Type(String), S(s) {}
    generic(std::string &&s) : Type(String), S(std::move(s)) {}
    generic(double x) : Type(Float), X(x) {}
  };

  namespace details {// auxiliary stuff
    template<typename T, typename E=void>
    struct traits
    {
      static constexpr generic::type Type=generic::Void;
      static void get(generic const&) {}
    };

    template<>
    struct traits<bool,void>
    {
      static constexpr generic::type Type=generic::Bool;
      static bool get(generic const&x) { return x.B; }
    };

    template<typename T>
    struct traits<T,enable_if_t<std::is_integral<T>::value>
    {
      static constexpr generic::type Type=generic::Int;
      static T get(generic const&x) { return x.I; }
    };

    template<>
    struct traits<std::string,void>
    {
      static constexpr generic::type Type=generic::Str;
      static std::string const& get(generic const&x) { return x.S; }
      static std::string&& get(generic&&x) { return std::move(x.S); }
    };

    template<T>
    struct traits<float,enable_if<std::is_same<T,float>::value ||
                                  std::is_same<T,double>::value>
    {
      static constexpr generic::type Type=generic::Float; };
      static T get(generic const&x) { return x.X; }
    }
  }

  template<typename T>
  auto unsafe_extract(generic const&x)
  -> decltype(details::traits<T>::get(x))
  { return details::traits<T>::get(x); }

  template<typename T>
  auto unsafe_extract(generic&&x)
  -> decltype(details::traits<T>::get(std::move(x)))
  { return details::traits<T>::get(std::move(x)); }

  template<typename T>
  auto extract(generic const&x)
  -> decltype(unsafe_extract(x))
  {
    if(details::traits<T>::Type != x.Type)
      throw std::runtime_error("type mismatch in extract(generic)");
    return unsafe_extract(x);
  }

  template<typename T>
  auto extract(generic&&x)
  -> decltype(unsafe_extract(std::move(x)))
  {
    if(details::traits<T>::Type != x.Type)
      throw std::runtime_error("type mismatch in extract(generic&&)");
    return unsafe_extract(std::move(x));
  }
}
using generic_type::generic;

然后您可以将数据存储在std::vector<generic>

如果你使用std :: tuple_cat,你应该能够将每个后续值添加到元组。 我还建议使用C ++ 14返回类型演绎,如果我是你,它消除了知道返回类型的需要。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM