如何使用 pybind11 为可变参数模板调用提供 Python 绑定?

问题描述 投票:0回答:1

我正在尝试使用 pybind11 为具有大量可变(成员)函数或构造函数的 C++17 库创建 Python 绑定。

// variadic member function
struct Bar
{
    template <typename... Args>
    void baz(Args&&...){ /*...*/ }
};

// variadic constructor
struct Foo
{
    template <typename... Args>
    Foo(Args&&...){ /*...*/ }
};

// variadic free function
template <typename... Args>
void foobar(Args&&...){ /*...*/ }

pybind11 supports positional

*args
arguments 使用这个功能对我来说似乎很自然。不幸的是,我一直无法弄清楚如何使用
pybind11::args
将参数转发给可变参数函数。

绑定代码是什么样的?

#include <pybind11/pybind11.h>
namespace py = pybind11;

PYBIND11_MODULE(example, m)
{
    py::class_<Bar>(m, "Bar")
    .def("baz", [](py::args){ /* how do I forward to variadic Bar::baz? */ });

    py::class_<Foo>(m, "Foo")
    .def(py::init<py::args>()); // is this right?

    m.def("foobar", [](py::args){ /* how do I forward to variadic foobar? */ });
}

这里有一些限制:

  • 对于我的用例,在所有情况下都支持某种固定类型的异构参数就足够了,例如我可以只为

    Foo
    公开一个接受任意数量的
    Bar
    实例的构造函数。

  • 我不能——或者非常不愿意——修改现有库的API。因此,必须使用接受例如一个

    std::initializer_list
    会让我难过。也就是说,绑定代码中的一些胶水代码是可以接受的,只要它是可维护的。

  • 虽然肯定不漂亮,但我可以接受将可变参数的数量限制为最大值,比如 20,因为模板必须在绑定代码的编译时显式实例化,例如通过

    template void foobar();
    template void foobar(Foo&&);
    // ...
    

    如果事实证明这是必要的,那么如果 python 用户能够得到一个适当的错误消息,表明已超过最大参数数量,那就太好了。

  • 它不一定是 pybind11,如果其他库处理得更好,我愿意使用它们。

python c++ c++17 variadic-templates pybind11
1个回答
0
投票

使用

py::cast
py::args
对象的每个元素转换为 int 并将它们作为参数传递给适当的 C++ 函数(这还需要在编译时了解类型): Playground

#include <pybind11/pybind11.h>

namespace py = pybind11;

struct Bar {
    void baz(int x, int y) { /* ... */ }
};

struct Foo {
    Foo(int x, int y) { /* ... */ }
};

void foobar(int x, int y) { /* ... */ }

// Explicitly instantiate the templates for up to 2 int arguments
template void Bar::baz<int, int>(int&&, int&&);
template void Foo::Foo<int, int>(int&&, int&&);
template void foobar<int, int>(int&&, int&&);

PYBIND11_MODULE(example, m) {
    py::class_<Bar>(m, "Bar")
        .def("baz", [](Bar& self, py::args args) {
            if (args.size() != 2) {
                throw std::runtime_error("Expected 2 arguments");
            }
            int x = py::cast<int>(args[0]);
            int y = py::cast<int>(args[1]);
            self.baz(std::move(x), std::move(y));
        });

    py::class_<Foo>(m, "Foo")
        .def(py::init<int, int>());

    m.def("foobar", [](py::args args) {
        if (args.size() != 2) {
            throw std::runtime_error("Expected 2 arguments");
        }
        int x = py::cast<int>(args[0]);
        int y = py::cast<int>(args[1]);
        foobar(std::move(x), std::move(y));
    });
}
© www.soinside.com 2019 - 2024. All rights reserved.