如何用对象作为参数编写一个variadic-template函数?

时间:2017-03-09 09:27:57

标签: c++ c++11 variadic-templates

考虑以下功能(它使用http://localhost:8080/invoice

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列中的值。 v0类型的Base *包含由value填充的成员read_row,并在process - 方法中处理。 Base是计算方法的接口类(例如:一个是Max,另一个是MinMaxAvg)。

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

read_headerread_row是variadic-template函数,因此可以接受任意数量的参数,但它们只适用于标量。

如何展开/解压缩variadic-argument以便它调用或使用成员?

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

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

5 个答案:

答案 0 :(得分:1)

一些精心设计的包装扩展将会花哨:

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

答案 1 :(得分:1)

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

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

答案 2 :(得分:1)

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

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&#39;答案),我们走了:

void process()
{ }

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

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

编辑:从J.Doe ......偷窃大小...(基地)+ 1。

答案 3 :(得分: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)...);
}

答案 4 :(得分:0)

可编译示例(您需要填写代码以便从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...);
  }
}