用模板参数包解析一行文本
Parse a line of text with template parameter pack
我有一些文件包含一些 space 分隔的数据。我想要一个只接受文件名的函数 parse
和一个作用于一行中所有元素的函数。例如,如果我有一个文件,其数据格式为
int float string
int float string
int float string
...
然后我想要一个函数 parse
接受包含此数据的文件名,以及一个将处理每一行的 lambda。如果我知道每行有多少元素(在本例中为 3),那么我可以这样做:
#include <fstream>
#include <sstream>
#include <iostream>
#include <string>
using namespace std;
template <typename Func, typename A, typename B, typename C>
void parse(const string & filename, Func func){
string line;
// TODO: Get line from file, not cin
// std::ifstream file(filename);
while (std::getline(cin, line)) {
stringstream ss(line);
A a;
B b;
C c;
ss >> a >> b >> c;
func( a, b, c );
}
}
int main()
{
auto forEach = [](int a, float b, string c){ cout << a << "," << b << "," << c << endl; };
parse<decltype(forEach),int,float,string>( "test.txt", forEach );
return 0;
}
上面的代码与参数的类型无关,但要求每行恰好有 3 个值。我想帮助将其扩展到每行参数数量为
的版本
- 从
Func
类型推断(这将是理想情况)。
- 指定为模板参数包。
例如,我会将案例 1 想象成
#include <fstream>
#include <sstream>
#include <iostream>
#include <string>
using namespace std;
template <typename Func>
void parse(const string & filename, Func func){
string line;
// TODO: Get line from file, not cin
// std::ifstream file(filename);
while (std::getline(cin, line)) {
stringstream ss(line);
// TODO Infer from the type Func that it requires
// int, float, string
// Then use a stringstream to parse those values from `line`
// and pass the results to func
func( ... );
}
}
int main()
{
auto forEach = [](int a, float b, string c){ cout << a << "," << b << "," << c << endl; };
parse<decltype(forEach)>( "test.txt", forEach );
return 0;
}
如果这不可能,那么我会接受使用参数包的解决方案。我只是不知道该怎么做。我想解决方案应该是这样的:
#include <fstream>
#include <sstream>
#include <iostream>
#include <string>
using namespace std;
template <typename Func, typename ...Args>
void parse(const string & filename, Func func){
string line;
// TODO: Get line from file, not cin
// std::ifstream file(filename);
while (std::getline(cin, line)) {
stringstream ss(line);
// TODO: Use Args to extract parse the appropriate number of
// parameters from `line` and pass the result to `func`
func( ... );
}
}
int main()
{
auto forEach = [](int a, float b, string c){ cout << a << "," << b << "," << c << endl; };
parse<decltype(forEach),int,float,string>( "test.txt", forEach );
return 0;
}
可以使用 operator()
(没有重载)或函数指针
找到函数特征
template<typename C> struct function_trait : function_trait<decltype(&C::operator())> {};
template <typename C, typename Ret, typename...Args>
struct function_trait<Ret (C::*)(Args...) const> : function_trait<Ret(Args...)> {};
// Handle volatile, reference on this, C-ellipsis combination...
template <typename Ret, typename...Args>
struct function_trait<Ret (*)(Args...)> : function_trait<Ret(Args...)> {};
template <typename Ret, typename...Args>
struct function_trait<Ret (Args...)>
{
using args = std::tuple<Args...>;
};
然后
template <typename Func, typename Tuple>
void parse(std::istream& is, Func func, Tuple t)
{
std::string line;
while (std::getline(is, line)) {
std::stringstream ss(line);
std::apply([&ss](auto&... args){ ((ss >> args), ...);}, t);
std::apply(func, t);
}
}
template <typename Func>
void parse(std::istream& is, Func func)
{
parse(is, func, typename function_trait<Func>::args{});
}
使用情况:
auto forEach = [](int a, float b, string c){ cout << a << "," << b << "," << c << endl; };
parse(std::cin, forEach );
Demo C++17
Demo C++14
对于 C++11,您必须实现 index_sequence
实用程序。
需要一些转换来处理具有 const 引用的函子,例如 auto forEach = [](int, float, const string&) {/*..*/}
:
template <typename T> struct tuple_decay;
template <typename... Ts> struct tuple_decay<std::tuple<Ts...>>
{
using type = std::tuple<std::decay_t<Ts>...>;
};
等替换:
parse(is, func, typename function_trait<Func>::args{});
来自
parse(is, func, typename tuple_decay<typename function_trait<Func>::args>::type{});
我有一些文件包含一些 space 分隔的数据。我想要一个只接受文件名的函数 parse
和一个作用于一行中所有元素的函数。例如,如果我有一个文件,其数据格式为
int float string
int float string
int float string
...
然后我想要一个函数 parse
接受包含此数据的文件名,以及一个将处理每一行的 lambda。如果我知道每行有多少元素(在本例中为 3),那么我可以这样做:
#include <fstream>
#include <sstream>
#include <iostream>
#include <string>
using namespace std;
template <typename Func, typename A, typename B, typename C>
void parse(const string & filename, Func func){
string line;
// TODO: Get line from file, not cin
// std::ifstream file(filename);
while (std::getline(cin, line)) {
stringstream ss(line);
A a;
B b;
C c;
ss >> a >> b >> c;
func( a, b, c );
}
}
int main()
{
auto forEach = [](int a, float b, string c){ cout << a << "," << b << "," << c << endl; };
parse<decltype(forEach),int,float,string>( "test.txt", forEach );
return 0;
}
上面的代码与参数的类型无关,但要求每行恰好有 3 个值。我想帮助将其扩展到每行参数数量为
的版本- 从
Func
类型推断(这将是理想情况)。 - 指定为模板参数包。
例如,我会将案例 1 想象成
#include <fstream>
#include <sstream>
#include <iostream>
#include <string>
using namespace std;
template <typename Func>
void parse(const string & filename, Func func){
string line;
// TODO: Get line from file, not cin
// std::ifstream file(filename);
while (std::getline(cin, line)) {
stringstream ss(line);
// TODO Infer from the type Func that it requires
// int, float, string
// Then use a stringstream to parse those values from `line`
// and pass the results to func
func( ... );
}
}
int main()
{
auto forEach = [](int a, float b, string c){ cout << a << "," << b << "," << c << endl; };
parse<decltype(forEach)>( "test.txt", forEach );
return 0;
}
如果这不可能,那么我会接受使用参数包的解决方案。我只是不知道该怎么做。我想解决方案应该是这样的:
#include <fstream>
#include <sstream>
#include <iostream>
#include <string>
using namespace std;
template <typename Func, typename ...Args>
void parse(const string & filename, Func func){
string line;
// TODO: Get line from file, not cin
// std::ifstream file(filename);
while (std::getline(cin, line)) {
stringstream ss(line);
// TODO: Use Args to extract parse the appropriate number of
// parameters from `line` and pass the result to `func`
func( ... );
}
}
int main()
{
auto forEach = [](int a, float b, string c){ cout << a << "," << b << "," << c << endl; };
parse<decltype(forEach),int,float,string>( "test.txt", forEach );
return 0;
}
可以使用 operator()
(没有重载)或函数指针
template<typename C> struct function_trait : function_trait<decltype(&C::operator())> {};
template <typename C, typename Ret, typename...Args>
struct function_trait<Ret (C::*)(Args...) const> : function_trait<Ret(Args...)> {};
// Handle volatile, reference on this, C-ellipsis combination...
template <typename Ret, typename...Args>
struct function_trait<Ret (*)(Args...)> : function_trait<Ret(Args...)> {};
template <typename Ret, typename...Args>
struct function_trait<Ret (Args...)>
{
using args = std::tuple<Args...>;
};
然后
template <typename Func, typename Tuple>
void parse(std::istream& is, Func func, Tuple t)
{
std::string line;
while (std::getline(is, line)) {
std::stringstream ss(line);
std::apply([&ss](auto&... args){ ((ss >> args), ...);}, t);
std::apply(func, t);
}
}
template <typename Func>
void parse(std::istream& is, Func func)
{
parse(is, func, typename function_trait<Func>::args{});
}
使用情况:
auto forEach = [](int a, float b, string c){ cout << a << "," << b << "," << c << endl; };
parse(std::cin, forEach );
Demo C++17
Demo C++14
对于 C++11,您必须实现 index_sequence
实用程序。
需要一些转换来处理具有 const 引用的函子,例如 auto forEach = [](int, float, const string&) {/*..*/}
:
template <typename T> struct tuple_decay;
template <typename... Ts> struct tuple_decay<std::tuple<Ts...>>
{
using type = std::tuple<std::decay_t<Ts>...>;
};
等替换:
parse(is, func, typename function_trait<Func>::args{});
来自
parse(is, func, typename tuple_decay<typename function_trait<Func>::args>::type{});