如何编写以对象为参数的可变模板函数?

How to write a variadic-template function with objects as parameters?

本文关键字:函数 何编写 对象 参数      更新时间:2023-10-16

考虑以下函数(它使用来自ben-strasser (github)的CSV解析器库)

void col1(const std::string &fn, Base *v0)
{
io::CSVReader<2> in(fn);
in.read_header(io::ignore_extra_column, "epoch", v0->column);
double ign;
while (in.read_row(ign, v0->value)) {
v0->process();
}
}
void col2(const std::string &fn, Base *v0, Base *v1)
{
io::CSVReader<3> in(fn);
in.read_header(io::ignore_extra_column, "epoch", v0->column, v1->column);
double ign;
while (in.read_row(ign, v0->value, v1->value)) {
v0->process();
v1->process();
}
}

此函数处理 CSV 文件的第 2 列中的值。Base *类型的v0包含由read_row填充并在process-方法中处理的成员valueBase是计算方法的接口类(例如:一个是Max,另一个是MinMaxAvg)。

如何重写此函数以接受任意数量的Base *参数以处理多个列?

read_headerread_row是可变模板函数,因此可以接受任意数量的参数,但它们仅适用于标量。

如何扩展/解压缩可变参数,以便它调用或使用成员?

我尝试了一些东西,阅读了一些示例,但我无法创建有效的东西,这是我当前的/嘲笑代码:

template<unsigned int COL>
void func(const std::string &fn, Base &... values)
{
io::CSVReader<COL> in(fn);
// that's it :-(
}

一些位置良好的包扩展将有效:

template <class... Bases>
void col(const std::string &fn, Bases *... bases)
{
io::CSVReader<sizeof...(Bases) + 1u> in(fn);
in.read_header(io::ignore_extra_column, "epoch", bases->column...);
double ign;
while (in.read_row(ign, bases->value...)) {
// Awful C++11 arbitrary expansion trick
int dum[]{ 0, (void(
bases->process()
), 0)... };
(void) dum;
// Alternative, sweet and beautiful C++17 fold expression
// (void)(bases->process(), ...);
}
}

两个步骤:首先,我们需要根据需要扩展我们的函数:

template <typename ... Bases>
void f(std::string const& s, Bases* ... values)
{
io::CSVReader<sizeof...(Bases) + 1> in(s);
in.read_header(io::ignore_extra_column, "epoch", values->column ...);
double ign;
while(in.read_row(ign, values->value ...))
{
/* see below! */ process(values...);
}
}

到目前为止没问题,read_header和read_row是可变参数模板,很好。但是,调用成员函数有点棘手 - 看看上面对(但未知的)进程函数的调用。关键字编译时递归(101010的答案),我们来了:

void process()
{ }
template <typename ... Bases>
void process(Base* b, Bases* ... values)
{
b->process();
process(values ...);
}

在模板函数之前定义这两个函数,它就可以工作了......

编辑:窃取大小...(基地)+ 1 来自 J.Doe...

使用打包扩展运算符...来解压缩可变参数。

template<typename... T> void nop(T&&...) { }
template<typename... Bases>
void func(const std::string &fn, Bases&&... bases)
{
io::CSVReader<sizeof...(Bases) + 1> in(fn);
in.read_header(io::ignore_extra_column, "epoch", bases->column...);
double ign;
while (in.read_row(ign, bases->value...)) {
// multiple ways to call process on all values
// prettier with C++17 stuff it seems
nop((bases->process(), 0)...);
}
}

使用可变参数模板,您必须实现一些较短的编译时递归:

template<unsigned int COL>
void func(const std::string &fn, Base &v) {
...
}
template<unsigned int COL, typename... Args>
void func(const std::string &fn, Base &v, Args&&... args) {
...
func<COL>(fn, std::forward<Args>(args)...);
}

可编译的示例(您需要填写代码以从csv文件读取并写入每个目标):

#include <string>
#include <cstdint>
#include <utility>
#include <tuple>

template<class Function, class...Ts>
void for_all(Function f, Ts&&...ts)
{
using expand = int[];
void(expand{0,
(f(std::forward<Ts>(ts)), 0)...
});
}
// some mocked io library
namespace io
{
template<std::size_t MaxRows>
struct CSVReader
{
CSVReader(const std::string& s)
{
}

template<class...Targets>
void read_headers(std::tuple<Targets...>& target)
{
read_headers_impl(std::make_index_sequence<sizeof...(Targets)>(), target);
}
template<class...Targets>
void read_row(std::tuple<Targets...>& targets)
{
read_values_impl(std::make_index_sequence<sizeof...(Targets)>(), targets);
}
// support for std::tie
template<class...Targets>
void read_row(const std::tuple<Targets...>& targets)
{
read_values_impl(std::make_index_sequence<sizeof...(Targets)>(), targets);
}
private:
template<std::size_t...Is, class Tuple>
void read_headers_impl(std::index_sequence<Is...>, Tuple& target)
{
for_all([](auto&& target) {
// read the header and assign it to target here
}, std::get<Is>(target)...);
}
template<std::size_t...Is, class Tuple>
void read_values_impl(std::index_sequence<Is...>, Tuple& target)
{
for_all([](auto&& target) {
// read the values and assign it to target here
}, std::get<Is>(target)...);
}
};
}
struct Base
{
std::string& value();
void process();
};
template<std::size_t N, class T, class Current = std::tuple<>> struct n_tuple;
template<std::size_t N, class T> using n_tuple_t = typename n_tuple<N, T>::type;
template<std::size_t N, class T, class Current>
struct n_tuple
{
using type = std::conditional_t<
N == std::tuple_size<Current>::value, 
Current, 
decltype(std::tuple_cat(std::declval<Current>(), std::declval<n_tuple_t<N-1, T>>()))
>;
};
template<class...Bases>
void col_n(const std::string &fn, Bases&...bases)
{
constexpr std::size_t column_count = sizeof...(Bases) + 1;
io::CSVReader<column_count> in(fn);
using headers_type = n_tuple_t<column_count, std::string>;
auto headers = headers_type();
in.read_headers(headers);
double ign;
auto value_refs = std::tie(ign, bases.value()...);
while (in.read_row(value_refs)) {
// now we only want to process each base
for_all([](auto&& base) {
base.process();
}, bases...);
}
}