我正在尝试使用 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,如果其他库处理得更好,我愿意使用它们。
使用
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));
});
}