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
Post a Comment