1. ホーム
  2. c++

[解決済み】複数のstd::collectionをCSVに出力する関数 / 1行目の反復処理

2022-02-17 23:34:59

質問

次のようなシグネチャ(またはそれに類するもの)を持つ関数を書きたい。

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

私が見た限りでは、バリアディックテンプレートは再帰を使って「反復」されるようですが、これでは「列が先」の反復順序を強いられることになります。私は「行が最初」の順序が必要で、バリアディックテンプレートはここでは間違ったツールかもしれないと思わせています。より良いアプローチはありますか?

どのように解決するのですか?

を使用することができます。 std::tuple のイテレータを格納するために、それぞれの containers を使用し、その後に std::apply というように、1つずつインクリメントしていきます。

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

デモ

C++23の導入に感謝 views::zip , collection_to_csv を使うだけで、よりシンプルに実装できます。 views::zip つまり、すべての containers を作成し、その要素を1つずつ繰り返し処理します。

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

デモ