current position:Home>Boost(7):Boost. Python encapsulates overloaded functions and passes default parameters

Boost(7):Boost. Python encapsulates overloaded functions and passes default parameters

2022-05-15 06:02:23Xiangdi

explain

We all know C++ It supports function overloading and default parameters , and Python The syntax itself does not support overloading , But because of the flexibility of its dynamic language , It can also be regarded as a feature of supporting overloading . So in use Boost.Python take C++ Package the program into Python Interface , How to deal with default parameters and function overloading ?

Boost.Python Two methods are provided to realize the encapsulation of overloaded functions : Manual packaging and automatic packaging . The latter is simple and can pass default parameters , But the functions that can be applied have limitations . The former can handle all kinds of overloaded functions , The default operation is a little troublesome and can't be passed . The comparison is as follows :

advantage shortcoming
Manual encapsulation It can be applied to various overloaded functions The default parameters will be lost , The operation is complicated
Automatic encapsulation You can keep the default parameters , It's easy to operate Overloaded functions can only be applied to functions with fixed parameter sequence and the same return value type

There is no absolute difference between the above two methods , In the development process , We often need to combine the two according to the actual situation .

Encapsulate overloaded functions

Manual encapsulation

With the following C++ Class, for example , Realize the operation of manually overloading functions .

class Test1
{
    
    public:
        bool func(int a) {
     return true; }
        bool func(int a, int b) {
     return true; }
        bool func (int a, float b, char c) {
     return true; }
        int func(int a, int b, int c) {
    return a + b + c; }
};

This class contains 4 An overloaded member function func().

First declare these member function pointer variables .

bool (Test1::*func1)(int) = &Test1::func;
bool (Test1::*func2)(int, int) = &Test1::func;
bool (Test1::*func3)(int, float, char) = &Test1::func;
int (Test1::*func4)(int, int, int) = &Test1::func;

And then in Python Encapsulate these function pointers in the module :

namespace bp = boost::python;

BOOST_PYTHON_MODULE(overload)
{
    
    bp::class_<Test1> ("Test1")
    .def ("func", func1, bp::args("a"))
    .def ("func", func2, bp::args("a", "b"))
    .def ("func", func3, bp::args("a", "b", "c"))
    .def ("func", func4, bp::args("a", "b"))
    ;
}

Run... After compilation :

$python
>>> import overload
>>> t1 = overload.Test1()
>>> t1.func(1)
True
>>> t1.func(1, 2)
True
>>> t1.func(1, 0.5, 'c')
True
>>> t1.func(1, 2, 3)
6

so , Using this method, the encapsulation of overloaded functions can be realized , But on the one hand, it's more troublesome , Each overloaded function needs to be declared as a separate pointer . On the other hand , If these functions contain default parameters, they cannot be passed to Python The method of .

Automatic encapsulation

Automatic encapsulation means that we no longer need to specify... Like manual encapsulation Python Which interface to call C++ function , This process consists of Boost.Python Help us finish .

Let's take another example , Define an overloaded function foo():

void foo ()
{
    
    std::cout << "The first foo has no args." << std::endl;
}

void foo (int a)
{
    
    std::cout << "The sencond foo has one arg: " << a << std::endl;
}

void foo (int a, float b)
{
    
    std::cout << "The third foo has two args: " << a << " " << b << std::endl;
}

void foo (int a, float b, char c)
{
    
    std::cout << "The forth foo has three args: " << a << " " << b << " " << c << std::endl;
}

foo() The function has four implementations , Each contains 0-3 Parameters .

stay Python Package in module :

using namespace boost::python;

BOOST_PYTHON_FUNCTION_OVERLOADS(foo_overloads, foo, 0, 3)

BOOST_PYTHON_MODULE(overload)
{
    
    def("foo", (void (*)(int, float, char))0, foo_overloads())
    ;
}

Use here BOOST_PYTHON_FUNCTION_OVERLOADS Macro to generate an overloaded function generator foo_overloads, The corresponding function is foo(), hinder 0 and 3 Represents the minimum and maximum number of parameters supported by the function respectively .
def() The function will automatically add all... For us foo variant . Original C++ The position of the function is replaced by the function pointer type , Followed by overloaded function generator foo_overloads.

Run... After compilation :

$ python

>>> import overload as ol
>>> ol.foo()
The first foo has no args.
>>> ol.foo(1)
The sencond foo has one arg: 1
>>> ol.foo(2, 0.5)
The third foo has two args: 2 0.5
>>> ol.foo(3, 0.4, 'c')
The forth foo has three args: 3 0.4 c
>>>


Limitations of automatic packaging

In the example above , We use auto encapsulation macro function to realize the automatic encapsulation of overloaded function , But take a closer look at the example above , Find out foo() The return type of all overloaded functions is void, And although it supports 0 - 3 A different number of parameters , But these parameters have a fixed order . That is, the latter function parameter is an extension of the previous function parameter sequence .
Suppose we modify the return value or parameter type of one of the functions , Such as :

int foo(int a);
//  perhaps 
void foo (int a, int b,int c)

Error will be reported in compilation :

error: no matching function for call to ‘foo(foo_overloads::non_void_return_type::gen<boost::mpl::vector4<void, int, float, char> >::T0&, foo_overloads::non_void_return_type::gen<boost::mpl::vector4<void, int, float, char> >::T1&)’
   86 | BOOST_PYTHON_FUNCTION_OVERLOADS(foo_overloads, foo, 0, 3)
...

error: return-statement with a value, in function returning ‘foo_overloads::non_void_return_type::gen<boost::mpl::vector4<void, int, float, char> >::RT’ {
    aka ‘void’} [-fpermissive]
   86 | BOOST_PYTHON_FUNCTION_OVERLOADS(foo_overloads, foo, 0, 3)
      | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
make[2]: *** [CMakeFiles/overload.dir/build.make:63: CMakeFiles/overload.dir/overload.cpp.o] Error 1
make[1]: *** [CMakeFiles/Makefile2:76: CMakeFiles/overload.dir/all] Error 2
make: *** [Makefile:84: all] Error 2

If it's in the original 4 Add a function based on one function , such as :

void foo (int a, char b)

Errors will also be reported when compiling .

thus it can be seen , Automatic encapsulation of overloaded functions requires the return value type of the objective function to be consistent , And follow the determined parameter sequence .

Pass default parameters

Manual transmission

In the first example , Use Boost.Python Manually encapsulating function pointers does not carry C++ Default parameter information of function .
For example, take a function with default parameters f:

int f(int, double = 3.14, char const* = "hello");

Pointing function f The type of the pointer to has no information about its default parameters :

int(*g)(int,double,char const*) = f;    //  There are no default parameters 

Then encapsulate the function pointer g Default parameters cannot be passed when :

def("g", g);                            //  Default parameters are missing 

therefore , If you want to pass the default parameter information to Python Interface , We need to be in the original C++ Based on the function, another set of functions :

// write "thin wrappers"
int f1(int x) {
     return f(x); }
int f2(int x, double y) {
     return f(x,y); }

/*...*/

// in module init
def("f", f);  // all arguments
def("f", f2); // two arguments
def("f", f1); // one argument

Two additional functions are defined here f1 and f2, among f1 Retain the f Two of the default parameters , f2 The last default parameter is retained .

Automatic delivery

Same as the previous auto encapsulation overloaded function , When automatically passing default parameters, we also need to use BOOST_PYTHON_FUNCTION_OVERLOADS Macro functions . for example , Given a function :

int foo(int a, char b = 1, unsigned c = 2, double d = 3)
{
    
    /*...*/
}

Call... Using macros :

BOOST_PYTHON_FUNCTION_OVERLOADS(foo_overloads, foo, 1, 4)

stay Python The commands in the module are the same as before :

def("foo", foo, foo_overloads());


Initialization and optional parameters

For class constructors, you can also use this tool to pass default parameters or a series of overloaded functions . Remember init<…> Do you ? for example , Given a class with a constructor X:

struct X
{
    
    X(int a, char b = 'D', std::string c = "constructor", double d = 0.0);
    /*...*/
}

We can easily add this constructor to Boost.Python:

.def(init<int, optional<char, std::string, double> >())
//  perhaps 
class_<X> ("X", init<int, optional<char, std::string, double> >())

Be careful : Use init<…> and optional<…> To represent the default value ( Optional parameters ).


Macro functions

The previous content basically introduces the processing of overloaded functions and default parameters in combination with examples , Here we will explain the functions and definitions of macro functions used earlier .

Macro use format

In the previous example, we only used BOOST_PYTHON_FUNCTION_OVERLOADS macro , This macro can only be used for ordinary functions or static functions , If the target is a member function of a class , You need to use its sister :BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS. The use format of these two macros is as follows :

// For global functions and static methods:
BOOST_PYTHON_FUNCTION_OVERLOADS( overloadsname , functionname , arg_minimum, arg_maximum )
BOOST_PYTHON_FUNCTION_OVERLOADS( overloadsname , classname::staticmethodname , arg_minimum, arg_maximum )

// For class methods:
BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS( overloadsname , classname::methodname, arg_minimum, arg_maximum )

The format and parameters are exactly the same :

  • overloadsname : Overload scheduling generator
  • functionname/staticmethodname/methodname : Functions to be encapsulated
  • arg_minimum : Minimum number of parameters
  • arg_maximum : The maximum number of parameters

Macro definition

Macros are defined in the file boost/python/detail/defaults_gen.hpp Header file , But in actual programming , We just need to include <boost/python/overloads.hpp> Just header file .

#define BOOST_PYTHON_FUNCTION_OVERLOADS(generator_name, fname, min_args, max_args) \ BOOST_PYTHON_GEN_FUNCTION_STUB( \ fname, \ generator_name, \ max_args, \ BOOST_PP_SUB_D(1, max_args, min_args))

#define BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(generator_name, fname, min_args, max_args) \ BOOST_PYTHON_GEN_MEM_FUNCTION_STUB( \ fname, \ generator_name, \ max_args, \ BOOST_PP_SUB_D(1, max_args, min_args))


Overload scheduling generator

Often mentioned in the previous description overloadsname/generator_name Overload scheduling generator , It will generate a series of overloaded methods for the extension class . Has the following properties :

  • docstring : And Python ‘_doc_’ Description of property binding ;
  • keywords : Keyword expression , Used to specify the parameters of the generation method ;
  • call policies : One CallPolices example ;
  • minimum arity : The minimum number of parameters that an overloaded function can accept ;
  • maximum arity : The maximum number of parameters accepted by overloaded functions ;

The definition of scheduling generator uses BOOST_PYTHON_, And its use can include the above properties ( You can also exclude ), As shown below :

overloadsname()
overloadsname(docstring)
overloadsname(docstring, keywords)
overloadsname(keywords, docstring)
overloadsname()[policies]
overloadsname(docstring)[policies]
overloadsname(docstring, keywords)[policies]
overloadsname(keywords, docstring)[policies]

See the following examples for specific applications .


Example

#include <boost/python/module.hpp>
#include <boost/python/def.hpp>
#include <boost/python/args.hpp>
#include <boost/python/tuple.hpp>
#include <boost/python/class.hpp>
#include <boost/python/overloads.hpp>
#include <boost/python/return_internal_reference.hpp>

using namespace boost::python;

tuple f(int x = 1, double y = 4.25, char const* z = "wow")
{
    
    return make_tuple(x, y, z);
}

BOOST_PYTHON_FUNCTION_OVERLOADS(f_overloads, f, 0, 3)

struct Y {
    };
struct X
{
    
    Y& f(int x, double y = 4.25, char const* z = "wow")
    {
    
        return inner;
    }
    Y inner;
};

BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(f_member_overloads, f, 1, 3)

BOOST_PYTHON_MODULE(args_ext)
{
    
    def("f", f,
        f_overloads(
            args("x", "y", "z"), "This is f's docstring"
        ));


    class_<Y>("Y")
        ;

    class_<X>("X", "This is X's docstring")
        .def("f1", &X::f,
                f_member_overloads(
                    args("x", "y", "z"), "f's docstring"
                )[return_internal_reference<>()]
        )
        ;
}


Reference material

boost.python FunctionOverloading
Overloading
boost/python/overloads.hpp

copyright notice
author[Xiangdi],Please bring the original link to reprint, thank you.
https://en.pythonmana.com/2022/131/202205110608081550.html

Random recommended