我正在开发一个可以使用MPI的工具,该工具将使用多个函数,每个函数可能彼此完全不同,比如签名和参数数量。C++版本不需要最低版本,我想这将与最新版本一起编译。
这个想法是,我将推送一些参数和一个函数ID,并最终序列化它们以通过MPI传递。它将由另一个进程接收,并使用一些反序列化程序来构建一个元组或参数包,该元组或参数组将传递给函数#ID。
我想创建一个函数向量,这样使用这个#ID(本质上是向量中的索引(,我可以选择相应的函数并传递前面提到的元组。
我的工具将接收这个函数向量,然后在编译时知道它们的类型,在编译时也知道元组类型,但要使用的函数和元组将取决于接收到的#ID。
我尝试过这样的std::变体,但我不知道如何使它工作,因为C++需要在编译时知道数据类型。
#include <iostream>
#include <functional>
#include <variant>
#include <vector>
int foo(int a) {
return a;
}
float bar(int a, float b) {
return (float) a * b;
}
float zor(float a, int b, float c) {
return a * (float) b * c;
}
template<typename F, typename Tuple, size_t... I>
static auto unpack_tuple(F &&f, Tuple &t, std::index_sequence<I...>) {
//I might have to ignore or to add some arguments in here,
//I know the existence of std::apply
return f(std::get<I>(t)...);
}
template<typename F, typename Tuple>
static auto unpack_tuple(F &&f, Tuple &t) {
static constexpr auto size = std::tuple_size<Tuple>::value;
return unpack_tuple(f, t, std::make_index_sequence<size>{});
}
template<typename Functions, typename Tuples>
void magicTool(Functions &functions, Tuples &tuples) {
// receivebuffer using MPI and function id
// use some deserializer and build arguments tuple, using Id to retrieve data types from tuples[id]
//idea
unpack_tuple(functions[id],tuple); //certainly error in this line
}
int main() {
typedef std::function<int(int)> Type1;
typedef std::function<float(int, float)> Type2;
typedef std::function<float(float, int, float)> Type3;
std::vector<std::variant<Type1, Type2, Type3>> functions;
typedef std::tuple<int> Tup1;
typedef std::tuple<int, float> Tup2;
typedef std::tuple<float, int, float> Tup3;
std::vector<std::variant<Tup1, Tup2, Tup3 >> tuples; //this could be also changed by tuple of tuples
functions.emplace_back(foo);
functions.emplace_back(bar);
functions.emplace_back(zor);
// initially just to let the compiler know their type, values won't
// be necessarly used
int a = 3;
float b = 6.4534;
auto t1 = std::make_tuple(a);
auto t2 = std::make_tuple(a, b);
auto t3 = std::make_tuple(b, a, b);
tuples.emplace_back(t1);
tuples.emplace_back(t2);
tuples.emplace_back(t3);
magicTool(functions,tuples);
return 0;
}
考虑到要扩展元组,通常通过创建递归助手并使用索引排序来完成,如以下
template<typename F, typename Tuple, size_t... I>
static auto unpack_tuple(F &&f, Tuple &t, std::index_sequence<I...>) {
return f(std::get<I>(t)...);
}
template<typename F, typename Tuple>
static auto unpack_tuple(F &&f, Tuple &t) {
static constexpr auto size = std::tuple_size<Tuple>::value;
return unpack_tuple(f, t, std::make_index_sequence<size>{});
}
但在这种情况下,我需要同时处理相同的变体和元组,所以最后我有这样的
Callable(args...); //Callable must match with parameter pack
我尝试了几个选项,但我总是遇到这样一个问题:我需要事先知道数据类型,并且不能使用某个条件,因为编译器可以匹配一个函数,并将其他函数抛出错误。
我该怎么做?
请在下面找到我想在C++中做什么的python剪辑。
import random
def foo(a: int): # function 0
return a
def bar(a: int, b: float): # function 1
return a*b
def zor(a: float, b: int, c: float): # function 2
return a*c+b
def forward(f, *args): # tuple forwarder to any function
return f(*args)
def magicTool(Callables):
packSize: int = random.randrange(3) # to emulate what function I will need
fId = packSize
print("random : {}".format(packSize))
argsPack = [] # to emulate the dynamic size of the incoming arguments
for i in range(packSize+1): # this only matches the parameter with the random function
arg = random.uniform(1.1, 10)
argsPack.append(arg)
print(forward(Callables[fId], *argsPack)) # this tries the function
myCallable = [foo, bar, zor]
magicTool(myCallable)
您可以使用std::apply
将元组作为参数传递给函数。
为了存储函数,您需要某种类型的擦除。在本例中,我选择了std::any
。
为了用ID存储函数,我使用了std::map。
#include <iostream>
#include <functional>
#include <any>
#include <map>
#include <tuple>
int foo(int val) {
return val;
}
float bar(float val1, int val2) {
return val1 * val2;
}
void zor(int i) {
std::cout << i << 'n';
}
struct FuncCollection {
std::map<int, std::function<std::any(std::any)>> funcMap;
template <typename Ret, typename... Args>
void addFunc(int id, Ret (*fPtr)(Args...)) {
funcMap[id] = [=](std::any args) -> std::any {
auto tuple = std::any_cast<std::tuple<Args...>>(args);
if constexpr(std::is_same_v<Ret, void>) {
std::apply(fPtr, tuple);
return 0;
} else {
return std::apply(fPtr, tuple);
}
};
}
template <typename... Args>
auto callFunc(int id, std::tuple<Args...> args) {
return funcMap[id](args);
}
};
int main()
{
FuncCollection fc;
fc.addFunc(1, foo);
fc.addFunc(2, bar);
fc.addFunc(3, zor);
std::tuple<int> p1{1};
std::tuple<float, int> p2{3.14, 2};
std::tuple<int> p3{5};
auto r1 = fc.callFunc(1, p1);
auto r2 = fc.callFunc(2, p2);
fc.callFunc(3, p3);
std::cout << std::any_cast<int>(r1) << ' ' << std::any_cast<float>(r2) << 'n';
}
这只是一个例子,尤其缺乏足够的错误检查。std::any_cast
将对无效的强制转换引发异常。