如何在可变参数模板函数中遍历可变参数元组?
How to iterate through a variadic tuple in a variadic template function?
我正在编写一个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;
}
相关文章:
- 在不传递参数数量且只有3个点的情况下,如何使用变差函数
- 如何使用可变参数模板强制转换每个变体类型
- 关于如何在具有单个参数的变体构造中选择替代方案?
- 调用参数排列不变函数 f(i++, i++)
- 参数归纳与标准::变体
- 模板化回调参数的逆变,如 C# 中的逆变
- 如何在没有参数包的情况下编写变差函数
- 通过具有嵌套类的工厂类获取多个变异类模板参数包
- 获取模板参数的成员变量值列表
- 保留短 lambda 用作函数的中间参数,使用 clang 格式保持不变
- 如何定义变体<x,y,z>提取模板参数的子类型
- 正确对齐内存模板,参数顺序不变
- 递归中不同参数类型的变元模板函数
- 通过函数指针传递给变差函数的参数会更改其值
- 提升预定义为带有参数的全局 lambda 的变体访问者
- 使用可变参数模板参数提升变体访问者
- boost ::变体 - 为什么模板参数比const字符串参数具有更高的优先级
- 将变参数包中的值加载到临时数组中
- 使用额外参数提升变体访客
- 正在将动态数组元素解析为参数?(变音符)