【问题标题】:How to iterate through a variadic tuple in a variadic template function?如何遍历可变参数模板函数中的可变参数元组?
【发布时间】:2020-01-31 18:21:32
【问题描述】:

我正在编写一个 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

这并不奇怪,我对这种语言还很陌生。我现在想知道如何以优雅的方式实现这个小功能。

感谢您的帮助。

【问题讨论】:

  • extract 的现有参数应该是正常的引用。 &amp;&amp; 在这里什么也做不了。顺便说一句:第 1 步:使用std::apply 将元组中的每个值通过引用 传递给template&lt;typename ...Args&gt; void helper(const std::string&amp; str, const char&amp; delimiter, Args &amp; ...args)。第 2 步:使用基本可变参数包技术实现helper() 进行提取。虽然通常在 stackoverflow.com 上,我们不会从头开始为其他人编写整个程序,但需要业力的人可能会这样做......
  • 谢谢,我去试试!我不是要求一个完整的计划,而是更多像你这样的指导方针;)

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


【解决方案1】:

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

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

【讨论】:

    【解决方案2】:
    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-matching 选项会有一个

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

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

    即将std::tuple&lt;std::optional&lt;Ts&gt;...&gt;&gt; 变成std::tuple&lt;Ts...&gt; 类似:

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

    【讨论】:

      【解决方案3】:

      好的,感谢社区的帮助,我的问题得到了解决。也许它会帮助某人理解可变参数模板函数,所以我将分享一个工作代码(基于 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;
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-04-02
        • 2019-06-06
        • 1970-01-01
        • 2011-11-06
        • 2021-07-28
        • 2016-12-01
        • 1970-01-01
        相关资源
        最近更新 更多