如何在可变参数模板函数中迭代可变参数元组?

时间:2020-01-31 18:21:32

标签: c++ variadic-templates variadic-functions stdtuple

我当时正在编写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

所以我想到了可变参数模板函数:

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 个答案:

答案 0 :(得分:3)

您可能会做类似的事情(我让您实现“解析”部分):

// 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);
}

答案 1 :(得分:0)

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...>>

或“如果不匹配则抛出”选项将具有

std::tuple<std::optional<Ts>...>
函数中的

,并且apply中的lambda将在找到元素时.emplace。然后在返回之前确保所有元素均有效,否则抛出或返回空的可选内容。

即,将std::tuple<std::optional<Ts>...>>转换为std::tuple<Ts...>,例如:

return std::apply( [](auto&&elems){ return std::make_tuple( *elems... ); }, splitted_line );

答案 2 :(得分:0)

好的,感谢社区的帮助,我的问题得以解决。也许它可以帮助某人理解可变参数模板函数,所以我将共享一个有效的代码(基于Adam Nevraumont的代码):

#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将字符串转换为所需的类型...也许如果您对元组中要处理的类型有更多控制,则必须实现另一个模板可变参数,然后进行专门化它(基于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;
}