在元组中迭代和调用异构函数
Iterating and invoking heterogeneous functions inside a tuple
在大多数情况下,我的问题如下:在编译时定义一系列异构函数指针(可能具有不同的arity),这些指针稍后需要在运行时以任意顺序迭代和调用。
将自己限制在C++中,什么是最合适的容器、迭代和调用机制?
这个问题是由现实世界的情况引起的,我后来发现了一个更简单的解决方案,不涉及元组,但它在本质上更专业。
最初我试图做这样的事情:
//type variables Y... have to be convertible to parameters of every function from the tuple std::tuple<T...> in order for this to compile
template<size_t n, typename... T, typename... Y>
void callFunNth(std::tuple<T...> &tpl, size_t i, Y... args) {
if (i == n)
std::get<n>(tpl)(args...);
else
callFunNth<(n < sizeof...(T)-1? n+1 : 0)>(tpl, i, args...);
}
template<typename... T, typename... Y>
void callFun(std::tuple<T...> &tpl, size_t i, Y... args) {
callFunNth<0>(tpl,i, args...);
}
int main()
{
using T1 = int;
namespace mpi = boost::mpi;
//Several instantiations of boost::mpi::reduce algorithm I am interested in
auto algs = make_tuple(boost::bind((void (*)(const mpi::communicator&, const T1*, T1, T1*, std::plus<T1>, int))mpi::reduce<T1, std::plus<T1>>, _1, _2, _3, _4, std::plus<T1>(), _5),
boost::bind((void (*)(const mpi::communicator&, const T1*, T1, T1*, mpi::minimum<T1>, int))mpi::reduce<T1, mpi::minimum<T1>>, _1, _2, _3, _4, mpi::minimum<T1>(), _5),
boost::bind((void (*)(const mpi::communicator&, const T1*, T1, T1*, std::minus<T1>, int))mpi::reduce<T1, std::minus<T1>>, _1, _2, _3, _4, std::minus<T1>(), _5)
);
//Iterate through the tuple and call each algorithm
for(size_t i=0; i < std::tuple_size<decltype(algs)>::value;i++)
callFun(algs, i, /*actual arguments to each algorithm*/);
}
这种方法的问题是,要使callFunNth在所有提供的参数上进行编译,必须将提供的参数类型转换为所提供元组内所有函数的参数,这严重限制了所述函数的异构性,并迫使用户使用std::bind或boost:::bind来解决此问题。
当类型可以相互转换时,可以编写以下内容:
template <typename T, typename U>
void fja(T x, U y) {
std::cout << x << std::endl;
}
auto funs = std::make_tuple(fja<int,std::string>, fja<double,std::string>, fja<char,std::string>);
callFun(funs, 2, 'a', "Char");
callFun(funs, 1, 2.45, "Decimal");
callFun(funs, 0, 1, "Integer");
并将"a"、"2.45"answers"1"分别发送到标准输出
您应该将函数对象存储为std::vector<std::function<const boost::mpi::communicator&, const T1*, int, T1*, int>>
。它更容易管理。
如果必须使用函数元组,请参阅下面的。
C++标准库严重需要一个编译时iota
如果您需要使用相同的参数来调用所有函数,那么这里有一个替代方法。首先,我们构造了可变整数列表integers<0, 1, 2, ..., n-1>
(从https://github.com/kennytm/utils/blob/master/vtmp.hpp):
template <size_t... ns>
struct integers
{
template <size_t n>
using push_back = integers<ns..., n>;
};
namespace xx_impl
{
template <size_t n>
struct iota_impl
{
typedef typename iota_impl<n-1>::type::template push_back<n-1> type;
};
template <>
struct iota_impl<0>
{
typedef integers<> type;
};
}
template <size_t n>
using iota = typename xx_impl::iota_impl<n>::type;
然后我们直接使用解压缩操作:
template <typename... T, size_t... ns, typename... Y>
void call_all_impl(const std::tuple<T...>& funcs,
const integers<ns...>&,
Y... args) {
__attribute__((unused))
auto f = {(std::get<ns>(funcs)(args...), 0)...};
}
template <typename T, typename... Y>
void call_all(const T& funcs, Y&&... args) {
call_all_impl(funcs,
iota<std::tuple_size<T>::value>(),
std::forward<Y>(args)...);
}
例如,
int main() {
call_all(std::make_tuple([](int x, double y){ printf("1: %d %gn", x, y); },
[](double x, int y){ printf("2: %e/%dn", x, y); },
[](int x, int y){ printf("3: %#x %#xn", x, y); }),
4, 9);
}
打印
1:492:40000000电子+00/93:0x4 0x9
稍微修改一下就可以使它只调用在运行时选择的第i
个参数。
template <typename... T, size_t... ns, typename... Y>
void call_one_impl(const std::tuple<T...>& funcs, size_t which,
const integers<ns...>&,
Y... args) {
__attribute__((unused))
auto f = {(ns == which && (std::get<ns>(funcs)(args...), 0))...};
}
template <typename T, typename... Y>
void call_one(const T& funcs, size_t which, Y&&... args) {
call_one_impl(funcs, which,
iota<std::tuple_size<T>::value>(),
std::forward<Y>(args)...);
}
例如,
int main() {
auto t = std::make_tuple([](int x, double y){ printf("1: %d %gn", x, y); },
[](double x, int y){ printf("2: %e/%dn", x, y); },
[](int x, int y){ printf("3: %#x %#xn", x, y); });
call_one(t, 2, 6.5, 7.5);
call_one(t, 0, 4, 9);
call_one(t, 1, 5.8, 8);
}
打印
3:0x6 0x71:492:5.800000e+00/8
异构容器的首选库是Boost.Fusion。正如你在该网站上看到的,它们使用编译时多态函数来执行这样的任务。
我认为使用元组存储函数是个坏主意
对我来说,创建函数指针更简单
typedef impl_defined pointer_to_function
并将功能推入
std::vector<pointer_to_function>
在对这个问题进行了更多的修改之后,我相信使用类型联合可能是解决这个问题的一个相当好的方法,尽管我确实担心目前形式中所述的问题有点过于模糊,不值得给出一个最佳答案。
无论如何,这就是在C++中使用类型联合来解决问题的方法:
int main()
{
auto f1 = [](int x) { std::cout << x << std::endl;};
auto f2 = [](int x, std::string y) { std::cout << y << std::endl;};
auto f3 = [](const std::vector<int> &x) { std::copy(x.begin(),x.end(),std::ostream_iterator<int>(std::cout," "));};
using tFun1 = decltype(f1);
using tFun2 = decltype(f2);
using tFun3 = decltype(f3);
using funUnion = boost::variant<tFun1,tFun2,tFun3>;
std::vector<funUnion> funs = {f1,f2,f3};
for(auto &ff : funs) {
if (tFun1* result = boost::get<tFun1>(&ff)) (*result)(2);
else
if (tFun2* result = boost::get<tFun2>(&ff)) (*result)(2,"Hello!");
else
if (tFun3* result = boost::get<tFun3>(&ff)) (*result)({1,2,3,4,5});
}
}
- 什么时候调用析构函数
- C++-明确何时以及如何调用析构函数
- C++ 防止在映射中放置()时调用析构函数
- 调用析构函数以释放动态分配的内存
- C++:使用方法调用析构函数的顺序是什么?
- 向量推回调用析构函数时调用析构函数
- 如何在调用析构函数时优雅地停止/销毁带有阻塞调用C++线程?
- 在C++中,如何创建包含可变模板对象的异构向量?
- 创建异构顶点数据数组的可移植方法
- std::variant vs指向C++中异构容器基类的指针
- C++,我应该调用析构函数吗?
- 如何获取有关在 Clang LibTooling 中调用析构函数的信息?
- 当我从 std::vector 中的新放置调用析构函数时会发生什么?
- 为什么这里不调用析构函数
- 在调用 std::bind 的产品后意外调用析构函数
- 为什么在传递给函数而不是构造函数时调用析构函数?
- 如何在C++中调用析构函数
- 为什么为未删除的对象调用析构函数?
- 如何在异构类的实例上循环调用具有相同名称和参数的方法
- 在元组中迭代和调用异构函数