Dmi*_*rov 5 c++ parsing templates variadic-templates c++11
我正在开发一个简单的 CSV 解析器,它将文件的行存储在一个元组中。如果不是因为文件内各行的条目数及其类型是变量,这将是一项简单的任务。因此,这些行可能是这样的:
1,2.2,你好,18,世界
解析器应该能够像这样工作:
ifstream file("input.csv");
SimpleCSVParser<int, float, string, int, string> parser(file);
Run Code Online (Sandbox Code Playgroud)
当我尝试实现一个函数来解析实际行时,事情变得复杂了。我仍然没有找到从参数列表中提取下一个类型以在调用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?
}
}
Run Code Online (Sandbox Code Playgroud)
但它不起作用,因为我用来提取类型的元组是空的。
我认为,您似乎尝试迭代元组索引/类型,但这是行不通的。然而,您可以做的就是为每个成员调用一个读取函数。这个想法是将元组的处理委托给一个函数,该函数使用参数包将操作扩展到每个元素上的操作。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
}
}
Run Code Online (Sandbox Code Playgroud)
上述代码的基本思想只是创建一个合适的调用序列read_tuple_element()。在进入通用代码之前,假设我们想要实现std::tuple<T0, T1, T2> value仅包含三个元素的 a 的读取。我们可以使用以下方法来实现读取(为简洁起见,使用rte()而不是read_tuple_element()):
rte(get<0>(value)), rte(get<1>(value)), rte(get<2>(value));
Run Code Online (Sandbox Code Playgroud)
现在,如果我们有一个索引序列,std::size_t... I我们可以[几乎]使用以下方式获得这个序列,而不是为每个元素数量写出这个序列
rte(get<I>(value))...;
Run Code Online (Sandbox Code Playgroud)
但是,不允许像这样扩展参数包。相反,参数包需要放入某些上下文中。上面的代码使用 astd::initializer_list<bool>来实现此目的:a 的元素std::initializer_list<T>按照列出的顺序构造。也就是说,我们得到了
std::initializer_list<bool>{ rte(get<I>(value))... };
Run Code Online (Sandbox Code Playgroud)
缺少的一点是如何创建I评估一系列合适索引的参数包。方便的是,标准库定义了为asstd::make_index_sequence<Size>创建一个std::index_sequence<I...>具有一系列值的。因此,调用with会创建一个带有合适参数列表的对象,这些参数可以被推导出来,然后用于将元组扩展为传递给 的元素序列。I0, 1, 2, ..., Size-1read_tuple_elements()std::make_index_sequence<sizeof...(Targs){}read_tuple_element()