I want to write a function with the following signature (or similar):
template <typename... Ts>
void collection_to_csv(const std::string filepath, const Ts& ... containers);
The function should write a csv file to a file located at filepath
, where containers can be any number of iteratable containers from the STD and would define the columns of the outputted CSV file. E.g. I could use it like this:
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);
Is this possible? So far I've been trying to use variadic templates like so:
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();
}
Everything I've seen about variadic templates suggests they're 'iterated' using recursion but this would force a 'column first' iteration order. I need a 'row first' order, making me think variadic templates might be the wrong tool here. Is there a better approach?
CodePudding user response:
Using a helper function with std::index_sequence
and unpacking on a lambda expression you can get it to work like 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);
}
Output:
1 1.5
2 2.5
3 3.5
4 4.5
Note, that it goes up to the shortest list and ignores the rest of the other lists.
Alternatively there are also some non-standard zip methods that can zip multiple collections in a single iterator in e.g. boost and some other libraries.
CodePudding user response:
You can use std::tuple
to store the iterator of each containers
and then use std::apply
to increment them one by one to do this.
#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);
}
Thanks to the introduction of C 23 views::zip
, collection_to_csv
can be implemented more simply just using views::zip
, that is, zip all containers
and iterate their elements one by one.
#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";
}
}