c++11 - Appropriate syntax for Initialising Python C-API container types (List Dict Tuple Set) in C++ -


i'm designing c++ python-wrapper.

i have object class wrap generic pyobject*, , providing constructors , conversion operators can things like:

// c++ type -> pyobject (of appropriate pyfoo_type) object i{42}; // create pyobject of type pylong_type object f{3.14}; // create pyobject of type pylong_type  // pyfoo_type -> c++ type std::string s{f}; // convert pyobject pystring_type, convert std::string 

i'm looking @ how might initialise containers: pydict_type pylist_type pytuple_type pyset_type (i think that's everything?)

it appears can break down 2 cases: pylist_type , pydict_type. because {pylist_type, pytuple_type, pyset_type} can initialise pylist_type , subsequently convert it.

my question is: what c++ syntax should provide?

seeing goal open-source project public consumption, need pay attention providing usable interface isn't going jar existing design patterns.

everything follows below thought process, , own attempt answer question. separate it. also, apologies may open question (more 1 solution).


(thoughts...)

let's want initialise list:

object l{1,2,3,"four",5}; // notice each item may of different type 

std::initializer_list<object> 1 option, , implicitly typecast every argument object

but 1 want object{1} yield? pyint_type. initializer_list constructor going overshadow/hide it.

and dictionaries:

object d{k1type k1:v1type v1, k2type k2:v2type v2, ... }; // illegal c++ syntax! 

again, key2 might not same type key1, that's great thing python.

in c++ don't think there's way use :, catch { {,}, {,}, {,} } std::map<object,object> or std::pair

so seems list biggest problem.

the thing can think of having first argument specify type:

object{ pylist_type, 1,2,3,"four",5 }; object{ pydict_type, k1, v1, k2, v2, etc }; 

this have advantage typing out dictionaries, braces cause clutter, although should allow for:

object{ std::map<object,object> }; 

so think complete set of constructors-for-containers this:

// list, tuple, set object( pytypeobject type_, std::initializer_list<object> args ) {...}   // dict { {,} , {,} , ... } style object( std::map<object,object> ) {...}   // dict { , , , ...} style (less typing) object( pytypeobject type_, std::map<object,object> ) {...}  

then symmetry should provide:

// object( pytypeobject type_, object& ob ) {...} 

... allowing user object f{pyfloat_type, 3} (3 converted pylong_type)

but wait! 3 not going implicitly converted object, implicit conversions work single-argument constructors.

object i{pyfloat_type, object{3}}; // bit yucky 

maybe should create makelong function, can do:

object f{ makefloat(3) }; 

but wait, can't use std::initializer_list constructor advantage here,

object f{ pyfloat_type, {3} }; 

and can code:

// list, tuple, set object( pytypeobject type_, std::initializer_list<object> args )  {     // first convert args pylist_type pyobject*, l     // if type_ isn't {pydict_type pylist_type pytuple_type pyset_type}     //      x = l[0] (and assert len(l)==1) else x = l     // python-runtime convert x "type_" } 

that going handle everything!

so first convert {3} list containing 1 pylong_type. pull out first (and only) element. convert pyfloat_type.

the first solution tried use std::initializer_list follows:

    object( pytypeobject& _type, std::initializer_list<object> args )     {         if( &_type == &pydict_type )         {             object dict{ pydict_new() };             for( auto p = args.begin(); p != args.end(); )             {                 object key{ *p }; p++; exception::wrap( p != args.end(), "dict init requires # of args" );                 object val{ *p }; p++;                  pyobject_setitem( dict.p, key.p, val.p );             }              *this = dict;              return;         }          // first unpack args pylist         object list{ pylist_new(0) };         for( auto ob : args )             pylist_append( list.p, ob.p );          if( &_type == &pylist_type          || &_type == &pytuple_type          || &_type == &pyset_type )         {             *this = list.convert_to(_type);              return;         }          *this = list[0];     }      object( const char c, std::initializer_list<object> args )     {         if( c=='l' ) *this = object{  pylist_type, args };         if( c=='t' ) *this = object{ pytuple_type, args };         if( c=='s' ) *this = object{   pyset_type, args };         if( c=='d' ) *this = object{  pydict_type, args };     } 

however, makes ugly syntax, fails convert arguments objects, appearing require:

 object{ 'l', { object{42}, object{3.14}, etc. } }; 

so settled on using variadic argument:

    template<typename foo, typename ... more>     void unpack_to_list( object& list, foo&& foo, more&& ... more ) {         pylist_append( list.p, object{foo}.p ); // *think* pylist_append expects neutral pointer         unpack_to_list( std::forward<more>(more) ... );     }      void unpack_to_list( object& list )  { } // recursion terminator      //template< car car, cdr ... cdr >     template<typename ... arg>     object( pytypeobject& _type, arg&& ... arg )     {         object list{ pylist_new(0) };         unpack_to_list( list, std::forward<arg>(arg) ... );         py_ssize_t n = pyobject_length( list.p );          if( &_type == &pydict_type )         {             exception::wrap( n % 2 == 0, "must supply number of arguments dictionary" );              object dict{ pydict_new() };              int i=0;             while( < n )                 pyobject_setitem( dict.p, list[i++].p, list[i++].p );              *this = dict;         }          else if(                 & _type == & pylist_type              || & _type == & pytuple_type              || & _type == & pyset_type    )         {             *this = list.convert_to(_type);              return;         }          else             *this = list[0];     }      template<typename ... arg>     object( const char c, arg&& ... arg )     {         if( c=='l' ) *this = object{  pylist_type, std::forward<arg>(arg) ... };         if( c=='t' ) *this = object{ pytuple_type, std::forward<arg>(arg) ... };         if( c=='s' ) *this = object{   pyset_type, std::forward<arg>(arg) ... };         if( c=='d' ) *this = object{  pydict_type, std::forward<arg>(arg) ... };     } 

this technique allows tidy lightweight usage syntax:

object mylist{ 'l', 42, 3.14, "foo", etc. }; object mydict{ 'd', "key1", val1, "key2", val2, etc. }; 

Comments

Popular posts from this blog

java - Plugin org.apache.maven.plugins:maven-install-plugin:2.4 or one of its dependencies could not be resolved -

Round ImageView Android -

How can I utilize Yahoo Weather API in android -