boost-python - предоставляет C ++ (не чистый) виртуальный метод с аргументами по умолчанию - PullRequest
0 голосов
/ 28 марта 2019

В boost-python для некоторого класса X рекомендуется использовать виртуальный метод, как показано ниже.

Я пытаюсь объединить это с функциональностью, чтобы указать параметры по умолчанию для этого виртуального метода. Это также поддерживается в документации Boost.

Однако не приведен пример использования виртуального метода, который также имеет параметры по умолчанию.

Я предположил, что класс-обертка должен также определить аргумент в качестве значения по умолчанию и передать его базовому getItem().

Аргумент по умолчанию - NULL-указатель, хотя у меня нет оснований подозревать (пока), что это актуально.

struct X_wrap : X, wrapper<X>                                                                                                                                                                                                                  
{                                                                                                                                                                                                                                                                   
    X_wrap(): X() {}                                                                                                                                                                                                                                  

    // getItem() is a non-Pure Virtual Function in Class X
    // It has a single argument a, which has a default value of 1                                                                                                                                                                                                           
    A* getItem(Z* a=NULL)                                                                                                                                                                                                                                              
    {                                                                                                                                                                                                                                                               
        if (override getItem = this->get_override("getItem"))                                                                                                                                                                                                       
            return getItem(a);                                                                                                                                                                                                                                       
        return X::getItem(a);                                                                                                                                                                                                                                 
    }                                                                                                                                                                                                                                                               
    A* default_getItem(Z* a=NULL) { return this->X::getItem(a); }                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          
};

Это тогда определяется как:

.def("getItem",                                                                                                                                                                                                                                      
     &X::getItem,                                                                                                                                                                                                                              
     &X_wrap::default_getItem);

Проблема в том, что параметр по умолчанию не будет переноситься как часть сигнатуры метода.

Boost предоставляет обходной путь для этого:

BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(getItem_member_overloads, getItem, 0, 1)

В невиртуальном случае ясно, как применить это к def:

.def("getItem",                                                                                                                                                                                                                                      
     &X::getItem,                                                                                                                                                                                                                              
     getItem_member_overloads());

Компилируется и работает как положено.

Однако, поскольку обертка и функция по умолчанию усложняют ситуацию, когда у нас есть виртуальная функция, не ясно, как их объединить. Я предполагаю, что вышеупомянутое не может быть правильным решением, так как я удалил default_getItem() из определения.

Это побудило меня попытаться создать второй набор перегрузок:

BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(default_getItem_overloads, default_getItem, 0, 1);

Макрос компилируется, но, кажется, не существует способа применить 2 отдельных набора перегрузок к .def(), который затем не завершится компиляцией.

Google предложил использовать boost::python::arg() и определить его как arg("a")=1:

Что-то вроде приведенных ниже компиляций:

.def("getItem",                                                                                                                                                                                                                                      
     &X::getItem,
     arg("a")=0,                                                                                                                                                                                                                     
     &X_wrap::default_getItem,
     arg("a")=0);

Но я получаю ошибку во время выполнения:

Boost.Python.ArgumentError: Python argument types in                                                                                                                                                                                                                
    X.getItem(ChildClassOfX)                                                                                                                                                                                                                          
did not match C++ signature:                                                                                                                                                                                                                                        
    getItem(X_wrap {lvalue}, Z* a=0)                                                                                                                                                                                                   
    getItem(X {lvalue}, Z* a=0)

Это говорит о том, что ChildClassOfX по какой-то причине не соответствует сигнатуре getItem() в базовом классе X.

На данный момент я немного офигеваю - и мои определения, вероятно, просты и неверны!

Пока что любое решение, которое у меня есть, либо каким-то образом нарушает полиморфизм времени выполнения, либо не компилируется.

Любые предложения или примеры будут огромной помощью!

(Примечание для Чистые Виртуальные функции. Отсутствие требования для функции по умолчанию означает, что в .def() передается только одна функция, так что легко изменить не виртуальный тривиальный пример - это дело не в не чистых виртуалах)

EDIT

Нашел в сети одну ссылку на кого-то, кто задает тот же вопрос - решение близко к моей попытке использовать аргументы, но, похоже, не работает и идет вразрез с текущей документацией Boost? Он использует класс-обертку в def() как для getItem(), так и default_getItem() с одним переданным набором аргументов - ниже приведен пример, приведенный в ссылке. Единственное другое отличие - это значение по умолчанию, а не указатель, как в моем случае:

def("override", WrapperClass::func, WrapperClass::default_func, (arg("x"), arg("y")=0, arg("z")=false))

Модификация для моего примера сборки в порядке, но выдает:

Boost.Python.ArgumentError: Python argument types in                                                                                                                                                                                                                
    X.getItem(ChildClassOfX)                                                                                                                                                                                                                          
did not match C++ signature:                                                                                                                                                                                                                                        
    getItem(X_wrap {lvalue}, Z* a=0)                                                                                                                                                                                                   
    getItem(X_wrap {lvalue}, Z* a=0)                                                                                                                                                                                                   

Ссылка: http://boost.2283326.n4.nabble.com/Boost-Python-inheritance-optional-parameters-td4592869.html

1 Ответ

0 голосов
/ 29 марта 2019

Я собрал воедино решение, которое, кажется, работает и подчиняется правилам полиморфизма.

Секрет в том, чтобы просто вообще не использовать boost::python::args или макрос BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS (хотя я допускаю, что он не будет поддерживать аргументы ключ-значение в Python как есть).

Просто создайте вспомогательную функцию, которая имеет l-значение, соответствующее классу, содержащему виртуальную функцию, т.е. X - и не имеет НИКАКИХ других аргументов, переданных ей. Это избавляет от проблемы Python, не соответствующей сигнатуре метода, потому что параметр a вместе со значением по умолчанию просто не существует:

A* getItem_noargs_wrap(X& x)
{
    return x.getItem();
}

Это кажется бессмысленным, но это совсем не так. Вызов x.getItem() - от C ++ до C ++, поэтому параметр по умолчанию правильно сопоставляется с пустой подписью.

Однако, когда мы пришли к написанию нашего def(), мы теперь можем дать Python функцию, которая действительно не принимает аргументов, что позволяет ему сопоставлять вызовы getItem() в Python.

Осталось лишь немного помочь компилятору узнать, какая подпись соответствует какому базовому вызову:

.def("getItem",                                                                                                                                                                                                                                      
     &getItem_noargs_wrap);
.def("getItem",                                                                                                                                                                                                                                      
     &X::getItem,                                                                                                                                                                                                                   
     &X_wrap::default_getItem);

Итак, getItem() дважды предоставляется Python - один раз без аргументов, используя нашу вспомогательную функцию для вызова метода правильного экземпляра за кулисами, и один раз взяв Z* и используя стандартный шаблон для нечистых виртуальных функций.

Второй вызов соответствует действию:

.def<const char* (X::*)(Z*)>("getItem",

Кажется, это работает хорошо, но я не проверил его полностью, он каким-то образом не нарушает полиморфизм.

...