函数向量、不同的签名和参数

Vector of functions, different signature and parameters

我正在开发一个可以与 MPI 一起使用的工具,这个工具将使用多个函数,每个函数可能彼此完全不同,比如签名和参数数量。 C++版本不需要最低版本,我假设这将与最新版本一起编译。

我的想法是,我将推送一些参数和一个函数 ID,并最终将它们序列化以通过 MPI 传递。它将被另一个进程接收,并通过一些反序列化器构建一个元组或参数包,该元组或参数包将传递给函数#ID。

我想创建一个函数向量,以便使用这个 #ID(本质上是向量中的索引)选择相应的函数并传递上述元组。

我的工具将接收这个函数向量,然后它们的类型将在编译时已知,元组类型也会在编译时已知,但要使用的函数和元组将取决于#ID收到。

我试过 std::variant 像这样,但我不知道如何让它工作,因为 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

我尝试了几个选项,但我总是遇到这样一个事实,即我需要事先知道数据类型,并且不能使用条件,因为编译器可以匹配一个函数并与其他函数一起抛出错误。

我该怎么做?

请在下面找到 python 我想用 C++ 做什么的片段。

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 将在无效转换时抛出异常。