【问题标题】:Vector of functions, different signature and parameters函数向量、不同的签名和参数
【发布时间】:2021-01-14 15:39:43
【问题描述】:

我正在开发一个可以与 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

我尝试了几个选项,但我总是发现我需要事先知道数据类型并且不能使用条件,因为编译器将能够匹配一个函数并与其他函数相匹配。

我该怎么做?

请在下面找到我想在 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)

【问题讨论】:

  • python snip of what I want to do in C++ 不同的是,python 有反射,c++ 没有。请在您的代码 sn-p 中添加缺少的 #includes 和函数 foobarzor 声明和简短的 int main() deifniiton,以便于使用。
  • 感谢@alex_noname 的评论,在这种情况下有一个条件,我必须明确地写出可能的类型。我正在尝试做的事情,“magicTool”不知道类型,并且要找出,它可能需要递归迭代,我不知道如何正确地做到这一点。
  • 如果你真的需要将所有函数存储在一个容器中,你可以使用类似std::function&lt;std::any(std::any)&gt; 的东西并将所有函数包装在一个 lambda 中,该 lambda 将 any_cast 传递给正确的参数元组并传递它到你的实际功能。
  • 旁白:我在这里看不到forward 的意义。 Callables[fId](*argsPack) 还不够吗?

标签: c++ tuples mpi c++20 variant


【解决方案1】:

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

【讨论】:

  • 拥有return 0 表明始终返回std::any 存在问题。我认为应该限制为void函数,或者返回特定类型
  • @Caleth 是的,它使代码不太干净,而且确实还有其他选择。不过,我会把这部分留给 OP,这至少是一种安全的工作方法。
猜你喜欢
  • 2016-01-01
  • 2023-02-07
  • 1970-01-01
  • 2015-11-30
  • 2023-03-06
  • 2018-02-04
  • 1970-01-01
  • 2013-11-25
  • 1970-01-01
相关资源
最近更新 更多