Как перебрать кортеж variadi c в шаблонной функции variadi c? - PullRequest
0 голосов
/ 31 января 2020

Я писал парсер 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

Итак, я подумал о функции шаблона variadi c:

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

Это не было большой неожиданностью, я довольно новичок в этом языке. Теперь я задаюсь вопросом, как выполнить эту маленькую функцию элегантным способом.

Спасибо за любую помощь.

Ответы [ 3 ]

3 голосов
/ 31 января 2020

Вы могли бы сделать что-то вроде (я позволю вам реализовать часть "синтаксического анализа"):

// 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);
}
0 голосов
/ 09 февраля 2020

Хорошо, благодаря помощи сообщества я решил свою проблему. Может быть, это поможет кому-то понять функции шаблона variadi c, поэтому я собираюсь поделиться рабочим кодом (на основе кода Адама Невраумона):

#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;
}

Как видите, я преобразую строку в тип, который я хочу с stringstream ... может быть, если у вас больше контроля над типом, который вы обрабатываете в кортеже, вам нужно реализовать другую функцию шаблона variadi c и затем специализировать ее (на основе кода 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;
}
0 голосов
/ 31 января 2020
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...>>

или Параметры throw-if-not-match будут иметь

std::tuple<std::optional<Ts>...>

внутри функции, а лямбда в apply будет .emplace элементом, когда он будет найден. Затем убедитесь, что все элементы действительны перед возвратом, иначе бросьте или верните пустой необязательный.

Ie, чтобы превратить std::tuple<std::optional<Ts>...>> в std::tuple<Ts...> что-то вроде:

return std::apply( [](auto&&elems){ return std::make_tuple( *elems... ); }, splitted_line );
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...