假设我的类型A
,B
包含构造函数A(int a, double b, std::string c)
,B(double a, int b)
。
我知道如何定义一个通过可变参数模板实例化A
或B
的函数。
有没有办法为类型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 ++中使用。
答案 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_for
和index_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"});
然而,初始化程序列表不会与容器混合,不知道如何解决这个问题。