Разбор строки текста с пакетом параметров шаблона - PullRequest
0 голосов
/ 07 января 2019

У меня есть несколько файлов с разделенными пробелами данными. Я хочу функцию parse, которая принимает только имя файла и функцию, которая действует на все элементы в одной строке. Например, если у меня есть файл с данными в формате

int float string
int float string
int float string
...

тогда я хочу иметь функцию parse, которая принимает имя файла, содержащего эти данные, а также лямбду, которая будет обрабатывать каждую строку. Если бы я знал, сколько элементов было в каждой строке (в данном случае 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;
}

1 Ответ

0 голосов
/ 07 января 2019

функциональные признаки можно найти с помощью 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 );

Демонстрация C ++ 17
Демонстрация C ++ 14
Для C ++ 11 вы должны реализовать утилиту index_sequence.

Некоторые преобразования необходимы для обработки функторов с константной ссылкой, таких как 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{});

Демонстрация (C ++ 17)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...