Связывание функции с аргументом std :: initializer_list с использованием pybind11

Я пытаюсь создать привязки python с помощью pybind11 (v2.2.2 +) и не могу понять, как вызвать функцию C, которая имеет единственный аргумент std :: initializer_list.

void list_ints(std::initializer_list<int>)

И привязка pybind11:

m.def("list_ints", &list_ints)

Из python я пытаюсь вызвать вот так:

list_ints(1, 2, 3)

Вот пример кода C, скомпилированного с использованием llvm в MacOS с -std=C++14:

#include <iostream>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>

using namespace std;
namespace py = pybind11;

void list_ints(const std::initializer_list<int> &il) {
    std::cout << "Got to list_ints ..." << std::endl;
    for (const auto &elem : il)
        std::cout << to_string(elem) << " ";
    std::cout << std::endl;
};

PYBIND11_MODULE(initializer, m) {
    m.def("list_ints", &list_ints);
    m.def("list_ints", (void (*) (const std::initializer_list<int>&)) &list_ints);
    # This is the only binding that seems to work .. sort of.
    m.def("list_ints", (void (*) (const int &a, const int &b)) &list_ints);
}

Код python содержит описание результатов:

from initializer import list_ints

try:
    # Fails with: TypeError: Incompatible function arguments
    print("Calling list_ints(1, 2, 3)")
    list_ints(1, 2, 3)
except TypeError as err:
    print(err)

# Call succeeds but function Seg Faults!
print("Calling list_ints(1, 2)")
list_ints(1,2)

Этот тестовый код демонстрирует, что привязка с аргументами, определенными как const int &a, const int &b, соответствует и вызывает функцию list_ints, но что-то явно не так, поскольку при доступе к аргументам возникает ошибка сегмента.

$ python initializer.py
Calling list_ints(1, 2, 3)
list_ints(): incompatible function arguments. The following argument types are supported:
    1. (arg0: std::initializer_list<int>) -> None
    2. (arg0: std::initializer_list<int>) -> None
    3. (arg0: int, arg1: int) -> None

Invoked with: 1, 2, 3

Did you forget to `#include <pybind11/stl.h>`? Or <pybind11/complex.h>,
<pybind11/functional.h>, <pybind11/chrono.h>, etc. Some automatic
conversions are optional and require extra headers to be included
when compiling your pybind11 module.
Calling list_ints(1, 2)
Got to list_ints ...
Segmentation fault: 11

Есть ли способ привязать и вызвать void list_ints(std::initializer_list<int>) из Python?


person Bob Barcklay    schedule 27.02.2018    source источник


Ответы (2)


Несмотря на выдвинутый аргумент «невозможно поддержать», на самом деле это довольно просто с cppyy (http://cppyy.org) , с оговоркой, что вместо list_ints(1, 2, 3) вам нужно использовать list_ints((1, 2, 3)). Т.е. используйте фактическую коллекцию python, а не 3 аргумента (сравните, как инициализируются массивы numpy; то же самое):

import cppyy

cppyy.cppdef(r"""void list_ints(std::initializer_list<int> ll) {
    for (auto i: ll)
        std::cerr << i << '\n';
}""")

cppyy.gbl.list_ints((1, 2, 3))

который печатает ожидаемый:

1
2
3
person Wim Lavrijsen    schedule 17.11.2019

Я получил этот ответ от [email protected] в репозитории pybind11:

Он не поддерживается и, я считаю, невозможно поддерживать: список инициализаторов - это намеренно непрозрачный тип, предназначенный для создания только компилятором C ++, но не кодом C ++ - а это означает, что для нас просто невозможно принять это.

См. Ответ на C ++ 11, можно ли построить std :: initializer_list? для получения дополнительных сведений.

Что касается кода привязки, вы в основном reintepret_cast превращаете свою функцию в функцию, которая принимает другой тип. Pybind конструирует std::vector<int>, затем передает его как аргумент функции, но функция думает, что получает _3 _--, и тогда происходят Плохие вещи. По сути, он выполняет auto &il = reintepret_cast<std::initializer_list<int> &>(v) в качестве аргумента, где v - это std::vector<int>.

person Bob Barcklay    schedule 28.02.2018