从构造函数参数的组合生成变量类型的对象

时间:2016-03-17 17:26:15

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

假设我的类型AB包含构造函数A(int a, double b, std::string c)B(double a, int b)

我知道如何定义一个通过可变参数模板实例化AB的函数。

有没有办法为类型T设计一个函数/宏/类型,并为T的构造函数参数提供一系列可能性向量,它为我提供了所有可能的对象?< / p>

例如,如果我对<A, {2, 5, 6}, {2.44, 3.14}, {"yes", "no"}>使用这个神奇的构造,它应该提供对象:

A(2, 2.44, "yes")
A(2, 2.44, "no")
A(2, 3.14, "yes")
...
A(6, 3.14, "no")

同样适用于B或任何其他类型,而无需重做神奇的构造。

例如,这在Python中非常简单,但我不知道它是否可以在C ++中使用。

6 个答案:

答案 0 :(得分:6)

这使用std::experimental::array_view来提高效率。您可以以某些运行时成本将其替换为std::vector,或者以一定的清晰度代价替换一对迭代器/指针。

template<class T>
using array_view = std::experimental::array_view<T>;

using indexes = array_view<std::size_t>;

这会迭代索引中相应索引的每个元素<的叉积。因此{3,3,2}is一样,{0,0,0},然后{0,0,1}一直到{2,2,1}

template<class F>
void for_each_cartesian_product(
  indexes is,
  F&& f,
  std::vector<std::size_t>& result
) {
  if (is.empty()) {
    f(result);
    return;
  }
  auto max_index = is.front();
  for (std::size_t i = 0; i < max_index; ++i) {
    result.push_back(i);
    for_each_cartesian_product( {is.begin()+1, is.end()}, f, result );
    result.pop_back();
  }
}
template<class F>
void for_each_cartesian_product(
  indexes is,
  F&& f
) {
  std::vector<size_t> buffer;
  for_each_cartesian_product( is, f, buffer );
}

然后我们只填充我们的索引:

template<class...Ts>
std::vector<std::size_t> get_indexes( std::vector<Ts> const&... vs ) {
  return {vs.size()...};
}

接下来,我们可以只需要接受我们的参数,将它们放在一个向量中,然后使用索引从每个向量中获取元素并将它们传递给A进行构造。

template<class T, std::size_t...Is, class...Args>
std::vector<T> make_stuff( std::index_sequence<Is...>, std::vector<Args>const&... args ) {
  std::vector<T> retval;
  for_each_cartesian_product(
    get_indexes(args...),
    [&](auto&& index){
      retval.emplace_back( args[ index[Is] ]... );
    }
  );
  return retval;
}
template<class T, class...Args>
std::vector<T> make_stuff( std::vector<Args>const&... args ) {
  return make_stuff<T>( std::index_sequence_for<Args...>{}, args... );
}

鲍勃是你的叔叔。

生成的A可能会被移动。

在编译时使用编译时已知数组执行此操作也可以完成。

index_sequence_forindex_sequence是C ++ 14,但很容易在C ++ 11中实现。堆栈溢出有很多例子。

上面的代码尚未编译。

答案 1 :(得分:3)

这是一个相对简单的方法:

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

using std::vector;
using std::cout;
using std::tie;


template <size_t index,size_t count>
struct Maker {
  template <typename T,typename Tuple,typename...Args>
  static void make(vector<T> &v,const Tuple &tup,Args &...args)
  {
    for (auto &x : std::get<index>(tup)) {
      Maker<index+1,count>::make(v,tup,args...,x);
    }
  }
};

template<size_t index>
struct Maker<index,index> {
  template <typename T,typename Tuple,typename...Args>
  static void make(vector<T> &v,const Tuple &,Args &...args)
  {
    v.push_back(T(args...));
  }
};

template <typename T,typename...Ts>
static vector<T> combinations(const Ts &...args)
{
  vector<T> v;
  Maker<0,sizeof...(args)>::make(v,tie(args...));
  return v;
}




int main()
{
  struct A {
    A(int,double,std::string) { }
  };

  struct B {
    B(double,int) { }
  };

  vector<A> as =
    combinations<A>(
      vector<int>{2,5,6},
      vector<double>{2.44,3.14},
      vector<const char *>{"yes","no"}
    );
  vector<B> bs =
    combinations<B>(
      vector<double>{2.44,3.14},
      vector<int>{2,5,6}
    );
  cout << "as.size()=" << as.size() << "\n";
  cout << "bs.size()=" << bs.size() << "\n";
}

输出:

12
6

答案 2 :(得分:2)

我的解决方案并不像Yacc那样优雅(毕竟我正在努力学习),但我认为它相对简单。

欢迎评论和报告缺陷。

#include <tuple>
#include <vector> 
#include <complex>
#include <iostream>
#include <initializer_list>


template <std::size_t ... N, typename X, typename ... Al>
void cartHelp (std::vector<X> & v,
               std::tuple<Al...> const & t)
 { v.emplace_back(std::get<N>(t)...); }


template <std::size_t ... N, typename X, typename ... Al1, typename L,
   typename ... Al2>
void cartHelp (std::vector<X> & v,
               std::tuple<Al1...> const & t, 
               std::initializer_list<L> const & l,
               std::initializer_list<Al2> const & ... al2)
 {
   for ( auto const & elem : l )
      cartHelp<N..., sizeof...(N)>(v, std::tuple_cat(t, std::tie(elem)),
                                   al2...);
 }



template <typename X, typename L, typename ... Al>
std::vector<X> cartesian (std::initializer_list<L> const & l,
                          std::initializer_list<Al> const & ... al)
 {
   std::vector<X>  v;

   for ( auto const & elem : l )
      cartHelp<0>(v, std::tie(elem), al...);

   return v;
 }


int main()
 {
   auto v1 = cartesian<int>({1,2});

   std::cout << "--- v1.size --- " << v1.size() << "\n";
   std::cout << "v1";
   for ( auto const & elem : v1 )
      std::cout << '[' << elem << ']';
   std::cout << '\n';

   auto v2 = cartesian<std::complex<double>>
      ({1.2,2.3,3.4}, {11.11, 22.22, 33.33});

   std::cout << "--- v2.size --- " << v2.size() << "\n";
   std::cout << "v2";
   for ( auto const & elem : v2 )
      std::cout << '[' << elem << ']';
   std::cout << '\n';

   auto v3 = cartesian<std::tuple<int, double, std::string>>
      ({1, 2, 3, 4, 5}, {0.1, 0.2, 0.3, 0.4},
       {std::string("aaa"), std::string("bbb"), std::string("ccc")});

   std::cout << "--- v3.size --- " << v3.size() << "\n";
   std::cout << "v3";
   for ( auto const & elem : v3 )
      std::cout << '[' << std::get<0>(elem) << ',' << std::get<1>(elem)
         << ',' << std::get<2>(elem) << ']';
   std::cout << '\n';

   return 0;
 }

输出:

--- v1.size --- 2
v1[1][2]
--- v2.size --- 9
v2[(1.2,11.11)][(1.2,22.22)][(1.2,33.33)][(2.3,11.11)][(2.3,22.22)][(2.3,33.33)][(3.4,11.11)][(3.4,22.22)][(3.4,33.33)]
--- v3.size --- 60
v3[1,0.1,aaa][1,0.1,bbb][1,0.1,ccc][1,0.2,aaa][1,0.2,bbb][1,0.2,ccc][1,0.3,aaa][1,0.3,bbb][1,0.3,ccc][1,0.4,aaa][1,0.4,bbb][1,0.4,ccc][2,0.1,aaa][2,0.1,bbb][2,0.1,ccc][2,0.2,aaa][2,0.2,bbb][2,0.2,ccc][2,0.3,aaa][2,0.3,bbb][2,0.3,ccc][2,0.4,aaa][2,0.4,bbb][2,0.4,ccc][3,0.1,aaa][3,0.1,bbb][3,0.1,ccc][3,0.2,aaa][3,0.2,bbb][3,0.2,ccc][3,0.3,aaa][3,0.3,bbb][3,0.3,ccc][3,0.4,aaa][3,0.4,bbb][3,0.4,ccc][4,0.1,aaa][4,0.1,bbb][4,0.1,ccc][4,0.2,aaa][4,0.2,bbb][4,0.2,ccc][4,0.3,aaa][4,0.3,bbb][4,0.3,ccc][4,0.4,aaa][4,0.4,bbb][4,0.4,ccc][5,0.1,aaa][5,0.1,bbb][5,0.1,ccc][5,0.2,aaa][5,0.2,bbb][5,0.2,ccc][5,0.3,aaa][5,0.3,bbb][5,0.3,ccc][5,0.4,aaa][5,0.4,bbb][5,0.4,ccc]

答案 3 :(得分:1)

以下是Vaughn Cato's solution的C ++ 14版本(信用到期)可以同时使用同类容器(包含单个类型的所有对象的容器)和< strong>异构 std::tuple容器(可能包含不同类型的对象)作为输入。这允许您混合和匹配参数类型以调用多个构造函数。

这可以在C ++ 11中实现,但是需要更多代码来模拟可变参数的通用lambda捕获。

#include <algorithm>
#include <tuple>
#include <vector>

template <typename Ts, size_t... Is, typename F>
void for_each_impl(Ts const& t, std::index_sequence<Is...> is, F const& f)
{
    using expand = int[];
    expand{ (f(std::get<Is>(t)), 0)... };
}

template <typename... Ts, typename F>
void for_each(std::tuple<Ts...> const& t, F const& f)
{
    for_each_impl(t, std::make_index_sequence<sizeof...(Ts)>(), f);
}

template <typename T, typename F>
void for_each(T const& t, F const& f)
{
    std::for_each(std::begin(t), std::end(t), f);
}

template <std::size_t Index, std::size_t Count>
struct cartesian_builder
{
    template <typename T, typename Sets, typename... Args>
    static void make(std::vector<T> &v, Sets const& sets, Args const&... args)
    {
        for_each(std::get<Index>(sets), [&](auto& arg) {
            cartesian_builder<Index + 1, Count>::make(v, sets, args..., arg);
        });
    }
};

template <std::size_t Count>
struct cartesian_builder<Count, Count> {
    template <typename T, typename Sets, typename... Args>
    static void make(std::vector<T>& v, Sets const&, Args const&... args)
    {
        v.emplace_back(args...);
    }
};

template <>
struct cartesian_builder<0, 0> {
    template <typename T, typename Sets, typename... Args>
    static void make(std::vector<T>&, Sets const&, Args const&...)
    {
    }
};

template <typename T, typename... Sets>
static std::vector<T> make_cartesian_product(Sets const&... sets)
{
    std::vector<T> v;
    cartesian_builder<0, sizeof...(sets)>::make(v, std::tie(sets...));
    return v;
}

用法示例:

#include <iostream>
#include <set>
#include <string>
#include <vector>

int main()
{
    struct A
    {
        int a;
        double b;
        std::string c;

        A(std::string a, double b, std::string c) :
            a(0), b(b), c(a + " " + c)
        {
        }

        A(int a, double b, std::string c) :
            a(a), b(b), c(c)
        {
        }
    };

    std::vector<A> objects = make_cartesian_product<A>(
        std::make_tuple(2, "maybe", 6),
        std::set<double>{2.44, 3.14},
        std::vector<char const*>{"yes", "no"}
    );

    for (auto& o : objects)
    {
        std::cout << "(" << o.a << ", " << o.b << ", " << o.c << ")\n";
    }
}

答案 4 :(得分:0)

您正在寻找的是“工厂”,根据您的实施情况,可能会或可能不会调用一个或多个构造函数。当然,如果A和B来自同一个父类(或者如果其中一个是另一个的后代),那么它是可能的。

编写一个可以构建所需对象集合的组工厂也是可行的。当然,决定如何存储指向新构造对象的指针是一个设计考虑因素。

我想更具体一点,但我至少需要看一下你所指的Python代码示例。

答案 5 :(得分:0)

auto variadic lambdas(C ++ 14)可用于存储参数引用而不是中间索引。

代码变得非常简单,不确定性能:

/** calls f for each element in c */
template <typename F, typename C>
void for_each_product(F&& f, const C& c) {
  for (auto& e: c) f(e);
}

/** calls f for each cartesian product from c,cs containers */
template <typename F, typename C, typename... Cs>
void for_each_product(F&& f, const C& c, const Cs&... cs) {
  for (auto& e: c)
    for_each_product(
             [&f, &e](auto&&... args) {
               f(e, args...);
             }, cs...);
}

这就是全部: - )

以下是如何使用它:

#include <vector>
#include <string>

struct A {
  int i;
  double d;
  std::string s;
  A(int _i, double _d, std::string _s): i(_i), d(_d), s(_s) {}
};

int main() {
  auto vi = std::vector<int>{2, 5, 6};
  auto vd = std::vector<double>{2.44, 3.14};
  auto vs = std::vector<std::string>{"yes", "no"};
  auto va = std::vector<A>();

  for_each_product([&va](auto&&... args){
      va.emplace_back(args...);
    }, vi, vd, vs);
}

对初始化列表的额外支持:

/** calls f for each element in c */
template <typename F, typename T>
void for_each_product(F&& f, const std::initializer_list<T>& c){
  for (auto& e: c) f(e);
}

/** calls f for each cartesian product from c,cs containers */
template <typename F, typename T, typename... Ts>
void for_each_product(F&& f, const std::initializer_list<T>& c,
              const std::initializer_list<Ts>&... cs){
  for (auto& e: c)
    for_each_product(
             [&f,&e](auto&&... args) {
               f(e, args...);
             }, cs...);
}

然后可以像这样使用它:

 for_each_product([&va](auto&&... args){ va.emplace_back(args...); },
    {2, 5, 6},
    {2.44, 3.14},
    {"yes", "no"});

然而,初始化程序列表不会与容器混合,不知道如何解决这个问题。