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

时间:2019-01-07 16:38:31

标签: c++ templates variadic-templates

我有一些带有空格分隔数据的文件。我想要一个仅接受文件名的函数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. (这是理想的情况)。
  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;
}

1 个答案:

答案 0 :(得分:4)

可以使用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)