我正在编写一个CSV解析器,我认为将一些高级C++付诸实践是个好主意。特别是,有一个有用的函数可以在给定分隔符的情况下拆分 CSV 文件的行。虽然这是一个可以直接编写的函数,但现在我希望该函数返回一个具有不同数量参数和类型的元组。例如:
int main() {
auto [a, b, c] = extract<int, std::string, float>("42;hello;3.1415", ';');
std::cout << a << ' ' << b << ' ' << c << std::endl;
}
应打印出来:
42 hello 3.1415
所以我想到了一个可变参数模板函数:
template <typename... T>
std::tuple<T...> extract(const std::string&& str, const char&& delimiter) {
std::tuple<T...> splited_line;
/* ... */
return splited_line;
}
但是我无法使用变量参数修改该函数中的元组,如下所示:
std::get<i>(splited_line) // doesn't work
这不是一个很大的惊喜,我对这种语言很陌生。我现在想知道如何以优雅的方式实现这个小功能。
感谢您的任何帮助。
你可以做类似的事情(我让你实现"解析"部分(:
// Parsing parts
std::vector<std::string> split(const std::string& s, char delimiter);
template <typename T>
T ConvertTo(const std::string& s);
// Variadic part
template <typename... Ts, std::size_t ... Is>
std::tuple<Ts...> extract_impl(std::index_sequence<Is...>,
const std::vector<std::string>& v)
{
return { ConvertTo<Ts>(v[Is])... };
}
template <typename... Ts>
std::tuple<Ts...> extract(const std::string& s, char delimiter) {
const auto strings = split(s, delimiter);
if (strings.size() != sizeof...(Ts)) {
// Error handling
// ...
}
return extract_impl<Ts...>(std::index_sequence_for<Ts...>(), strings);
}
template<class F>
auto foreach_argument( F&& f ) {
return [f = std::forward<F>(f)](auto&&...elems) {
( (void)f(elems), ... );
};
}
template <class... Ts>
std::tuple<Ts...> extract(const std::string& str, const char delimiter) {
std::tuple<Ts...> splited_line;
std::size_t i = 0;
std::size_t index = 0;
auto operation = [&](auto&& elem){
if (index == std::string::npos)
return;
auto next = str.find( delimiter, index );
std::string element = str.substr( index, next );
index = next;
// parse the string "element" into the argument "elem"
++i;
};
std::apply(foreach_argument(operation), splitted_line);
return splited_line;
}
这会导致默认构造的Ts
首先,如果未找到该元素,它将保持默认构造。
返回值
std::optional<std::tuple<Ts...>>
或者抛出不匹配的选项将具有
std::tuple<std::optional<Ts>...>
在函数中,apply
中的 lambda 将在找到元素时.emplace
该元素。 然后在返回之前确保所有元素都有效,否则抛出或返回空的可选。
即,将std::tuple<std::optional<Ts>...>>
变成std::tuple<Ts...>
,如下所示:
return std::apply( [](auto&&elems){ return std::make_tuple( *elems... ); }, splitted_line );
好的,多亏了社区的帮助,我的问题解决了。也许它会帮助某人理解可变参数模板函数,所以我将分享一个工作代码(基于 Adam Nevraumont 的代码(:
#include <iostream>
#include <string>
#include <tuple>
#include <string_view>
#include <sstream>
template <typename... Ts>
std::tuple<Ts...> extract(std::string_view str, char delimiter = ';') {
size_t idx = 0;
auto pop = [&](auto&& elem) {
auto next = str.find(delimiter, idx);
std::stringstream ss;
ss << str.substr(idx, next - idx);
ss >> elem;
idx = next + 1;
};
std::tuple<Ts...> splited;
std::apply([&](auto&&...elems) { (pop(elems), ...); }, splited);
return splited;
}
int main() {
std::string dataline = "-42;hello;3.1415;c";
auto [i, s, f, c] = extract<int, std::string, float, char>(dataline);
std::cout << i << " " << s << " " << f << " " << c << std::endl;
}
如您所见,我使用字符串流将字符串转换为我想要的类型......也许如果你对你在元组中处理的类型有更多的控制权,你必须实现另一个模板可变参数函数,然后专门化它(基于 Jarod42 的代码(:
#include <iostream>
#include <string>
#include <tuple>
#include <string_view>
template <typename T> T convert_to(const std::string_view& s) { return T(); } // default constructor
template <> std::string convert_to(const std::string_view& s) { return std::string(s); }
template <> float convert_to(const std::string_view& s) { return std::stof(std::string(s)); }
template <> int convert_to(const std::string_view& s) { return std::stoi(std::string(s)); }
template <typename... Ts, size_t... Is>
std::tuple<Ts...> extract_impl(std::index_sequence<Is...>,
std::string_view splited[sizeof...(Ts)]) {
return { convert_to<Ts>(splited[Is])... };
}
template <typename... Ts>
std::tuple<Ts...> extract(std::string_view str, char delimiter = ';') {
std::string_view splited[sizeof...(Ts)];
for (size_t i = 0, idx = 0; i < sizeof...(Ts); ++i) {
auto next = str.find(delimiter, idx);
splited[i] = str.substr(idx, next - idx);
idx = next + 1;
}
return extract_impl<Ts...>(std::index_sequence_for<Ts...>(), splited);
}
int main() {
auto [a, b, c] = extract<int, std::string, float>("-42;hello;3.1415");
std::cout << a << ' ' << b << ' ' << c;
}