用模板参数包解析一行文本

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 个值。我想帮助将其扩展到每行参数数量为

的版本
  1. Func 类型推断(这将是理想情况)。
  2. 指定为模板参数包。

例如,我会将案例 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{});

Demo (C++17)