输出几个 std::collections 到 CSV / 行第一次迭代的函数

Function which outputs several std::collections to CSV / row first iteration

我想编写一个具有以下签名(或类似签名)的函数:

template <typename... Ts>
void collection_to_csv(const std::string filepath, const Ts& ... containers);

该函数应将 csv 文件写入位于 filepath 的文件,其中容器可以是来自 STD 的任意数量的可迭代容器,并将定义输出的 CSV 文件的列。例如。我可以这样使用它:

std::vector<int> column1{1, 2, 3, 4};
std::list<float> column2{1.5, 2.5, 3.5, 4.5};
collections_to_csv(someFilePath, c1, c2);

这可能吗?到目前为止,我一直在尝试像这样使用可变参数模板:

    template <typename... Ts>
    void collection_to_csv(const std::string filepath, std::initializer_list<std::string> headers, const Ts& ... containers)
    {
        std::ofstream file;
        file.open(filepath, std::ofstream::out | std::ofstream::trunc);

        for (std::size_t irow=0; irow < size; ++irow)
        {
            for (const auto& container : containers) //But how to iterate through the variadic arguments when they could be different types?
            {
                file << container[i] << ",";
            }
        }

        file.flush();
        file.close();
    }

我所看到的关于可变参数模板的所有内容都表明它们 'iterated' 使用递归,但这会强制执行 'column first' 迭代顺序。我需要一个 'row first' 订单,这让我觉得这里的可变参数模板可能是错误的工具。有没有更好的方法?

您可以使用 std::tuple 存储每个 containers 的迭代器,然后使用 std::apply 将它们一一递增。

#include <iostream>
#include <string>
#include <tuple>
#include <vector>
#include <list>

template <typename... Ts>
void collection_to_csv(const Ts&... containers) {
  const auto ncol = [](const auto& first, const auto&...) { 
    return first.size(); 
  }(containers...);

  auto iters = std::make_tuple(containers.begin()...);

  for (std::size_t icol = 0; icol < ncol; ++icol) {
    std::apply(
      [&](auto&... iter) { 
        ((std::cout << *iter++ << ","), ...); 
    }, iters);
    std::cout << "\n";
  }
}

int main() {
  std::vector column1{1, 2, 3, 4};
  std::list column2{1.5, 2.5, 3.5, 4.5};
  collection_to_csv(column1, column2);
}

Demo.

感谢C++23的引入views::zipcollection_to_csv可以更简单的实现只用views::zip,即zip all containers并迭代他们的元素一一。

#include <ranges>
#include <tuple>

template <typename... Ts>
void collection_to_csv(const Ts&... containers) {
  for (const auto& values : std::ranges::zip_view(containers...)) {
    std::apply(
      [](const auto&... value) { ((std::cout << value << ","), ...); }, values);
    std::cout << "\n";
  }
}

Demo.

使用带有 std::index_sequence 的辅助函数并在 lambda 表达式上解包,你可以让它像 this:

一样工作
#include <cstddef>
#include <iostream>
#include <list>
#include <tuple>
#include <utility>
#include <vector>

template <typename... TIters, std::size_t... Indices>
void collections_to_csv_helper(std::tuple<TIters...> begins,
                               std::tuple<TIters...> ends,
                               std::index_sequence<Indices...>) {
    auto currents = begins;
    while (((std::get<Indices>(currents) != std::get<Indices>(ends)) && ...)) {
        (([&] {
             auto& current = std::get<Indices>(currents);
             std::cout << *current << '\t';
             current++;
         }()),
         ...);

        std::cout << '\n';
    }
}

template <typename... Containers>
void collections_to_csv(const Containers&... containers) {
    collections_to_csv_helper(std::tuple{containers.begin()...},
                              std::tuple{containers.end()...},
                              std::index_sequence_for<Containers...>{});
}

int main() {
    std::vector<int> column1{1, 2, 3, 4};
    std::list<float> column2{1.5, 2.5, 3.5, 4.5, 5.5};
    collections_to_csv(column1, column2);
}

输出:

1   1.5 
2   2.5 
3   3.5 
4   4.5 

请注意,它上升到 最短 列表并忽略其他列表的其余部分。

或者,还有一些非标准的 zip 方法可以在单个迭代器中压缩多个集合,例如boost 和其他一些库。