Commit 678d787c by Wenzel Jakob

do more work with classes from pytypes.h (especially for STL container casting)

parent d561cb01
...@@ -22,13 +22,13 @@ C++11-compatible compilers are widely available, this heavy machinery has ...@@ -22,13 +22,13 @@ C++11-compatible compilers are widely available, this heavy machinery has
become an excessively large and unnecessary dependency. become an excessively large and unnecessary dependency.
Think of this library as a tiny self-contained version of Boost.Python with Think of this library as a tiny self-contained version of Boost.Python with
everything stripped away that isn't relevant for binding generation. The core everything stripped away that isn't relevant for binding generation. Without
header files only require ~2.5K lines of code and depend on Python (2.7 or 3.x) comments, the core header files only require ~2.5K lines of code and depend on
and the C++ standard library. This compact implementation was possible thanks Python (2.7 or 3.x) and the C++ standard library. This compact implementation
to some of the new C++11 language features (specifically: tuples, lambda was possible thanks to some of the new C++11 language features (specifically:
functions and variadic templates). Since its creation, this library has grown tuples, lambda functions and variadic templates). Since its creation, this
beyond Boost.Python in many ways, leading to dramatically simpler binding code library has grown beyond Boost.Python in many ways, leading to dramatically
in many common situations. simpler binding code in many common situations.
Tutorial and reference documentation is provided at Tutorial and reference documentation is provided at
[http://pybind11.readthedocs.org/en/latest](http://pybind11.readthedocs.org/en/latest). [http://pybind11.readthedocs.org/en/latest](http://pybind11.readthedocs.org/en/latest).
......
...@@ -19,13 +19,13 @@ C++11-compatible compilers are widely available, this heavy machinery has ...@@ -19,13 +19,13 @@ C++11-compatible compilers are widely available, this heavy machinery has
become an excessively large and unnecessary dependency. become an excessively large and unnecessary dependency.
Think of this library as a tiny self-contained version of Boost.Python with Think of this library as a tiny self-contained version of Boost.Python with
everything stripped away that isn't relevant for binding generation. The core everything stripped away that isn't relevant for binding generation. Without
header files only require ~2.5K lines of code and depend on Python (2.7 or 3.x) comments, the core header files only require ~2.5K lines of code and depend on
and the C++ standard library. This compact implementation was possible thanks Python (2.7 or 3.x) and the C++ standard library. This compact implementation
to some of the new C++11 language features (specifically: tuples, lambda was possible thanks to some of the new C++11 language features (specifically:
functions and variadic templates). Since its creation, this library has grown tuples, lambda functions and variadic templates). Since its creation, this
beyond Boost.Python in many ways, leading to dramatically simpler binding code library has grown beyond Boost.Python in many ways, leading to dramatically
in many common situations. simpler binding code in many common situations.
Core features Core features
************* *************
......
...@@ -10,7 +10,7 @@ test_function(enum=1) ...@@ -10,7 +10,7 @@ test_function(enum=1)
None None
test_function(enum=2) test_function(enum=2)
None None
<class 'Example4.EMode'> <class 'example.EMode'>
EMode.EFirstMode EMode.EFirstMode
EMode.EFirstMode EMode.EFirstMode
Example4::test_function(enum=1) Example4::test_function(enum=1)
......
...@@ -22,6 +22,8 @@ def sanitize(lines): ...@@ -22,6 +22,8 @@ def sanitize(lines):
line = line.replace('__builtin__', 'builtins') line = line.replace('__builtin__', 'builtins')
line = line.replace('example.', '') line = line.replace('example.', '')
line = line.replace('unicode', 'str') line = line.replace('unicode', 'str')
line = line.replace('Example4.EMode', 'EMode')
line = line.replace('example.EMode', 'EMode')
line = line.replace('method of builtins.PyCapsule instance', '') line = line.replace('method of builtins.PyCapsule instance', '')
line = line.strip() line = line.strip()
if sys.platform == 'win32': if sys.platform == 'win32':
......
...@@ -225,8 +225,8 @@ struct argument_entry { ...@@ -225,8 +225,8 @@ struct argument_entry {
/// Internal data struture used to track registered instances and types /// Internal data struture used to track registered instances and types
struct internals { struct internals {
std::unordered_map<const void *, void*> registered_types_cpp; // std::type_info* -> type_info std::unordered_map<const void *, void*> registered_types_cpp; // std::type_info* -> type_info
std::unordered_map<const void *, void*> registered_types_py; // PyTypeObject* -> type_info std::unordered_map<const void *, void*> registered_types_py; // PyTypeObject* -> type_info
std::unordered_map<const void *, void*> registered_instances; // void * -> PyObject* std::unordered_map<const void *, void*> registered_instances; // void * -> PyObject*
std::unordered_set<std::pair<const PyObject *, const char *>, overload_hash> inactive_overload_cache; std::unordered_set<std::pair<const PyObject *, const char *>, overload_hash> inactive_overload_cache;
}; };
...@@ -271,4 +271,7 @@ struct error_already_set : public std::runtime_error { public: error_already_set ...@@ -271,4 +271,7 @@ struct error_already_set : public std::runtime_error { public: error_already_set
/// Thrown when pybind11::cast or handle::call fail due to a type casting error /// Thrown when pybind11::cast or handle::call fail due to a type casting error
struct cast_error : public std::runtime_error { public: cast_error(const std::string &w = "") : std::runtime_error(w) {} }; struct cast_error : public std::runtime_error { public: cast_error(const std::string &w = "") : std::runtime_error(w) {} };
PYBIND11_NOINLINE inline void pybind11_fail(const char *reason) { throw std::runtime_error(reason); }
PYBIND11_NOINLINE inline void pybind11_fail(const std::string &reason) { throw std::runtime_error(reason); }
NAMESPACE_END(pybind11) NAMESPACE_END(pybind11)
...@@ -24,7 +24,6 @@ ...@@ -24,7 +24,6 @@
#endif #endif
#include "cast.h" #include "cast.h"
#include <iostream>
NAMESPACE_BEGIN(pybind11) NAMESPACE_BEGIN(pybind11)
...@@ -196,9 +195,9 @@ protected: ...@@ -196,9 +195,9 @@ protected:
a.value, return_value_policy::automatic, nullptr); a.value, return_value_policy::automatic, nullptr);
if (obj == nullptr) if (obj == nullptr)
throw std::runtime_error("arg(): could not convert default keyword " pybind11_fail("arg(): could not convert default keyword "
"argument into a Python object (type not " "argument into a Python object (type not "
"registered yet?)"); "registered yet?)");
entry->args.emplace_back(a.name, a.descr, obj); entry->args.emplace_back(a.name, a.descr, obj);
} }
...@@ -490,7 +489,7 @@ protected: ...@@ -490,7 +489,7 @@ protected:
} else if (c == '%') { } else if (c == '%') {
const std::type_info *t = types[type_index++]; const std::type_info *t = types[type_index++];
if (!t) if (!t)
throw std::runtime_error("Internal error while parsing type signature (1)"); pybind11_fail("Internal error while parsing type signature (1)");
auto it = registered_types.find(t); auto it = registered_types.find(t);
if (it != registered_types.end()) { if (it != registered_types.end()) {
signature += ((const detail::type_info *) it->second)->type->tp_name; signature += ((const detail::type_info *) it->second)->type->tp_name;
...@@ -504,7 +503,7 @@ protected: ...@@ -504,7 +503,7 @@ protected:
} }
} }
if (type_depth != 0 || types[type_index] != nullptr) if (type_depth != 0 || types[type_index] != nullptr)
throw std::runtime_error("Internal error while parsing type signature (2)"); pybind11_fail("Internal error while parsing type signature (2)");
#if !defined(PYBIND11_CPP14) #if !defined(PYBIND11_CPP14)
delete[] types; delete[] types;
...@@ -519,7 +518,7 @@ protected: ...@@ -519,7 +518,7 @@ protected:
#endif #endif
if (!m_entry->args.empty() && (int) m_entry->args.size() != args) if (!m_entry->args.empty() && (int) m_entry->args.size() != args)
throw std::runtime_error( pybind11_fail(
"cpp_function(): function \"" + std::string(m_entry->name) + "\" takes " + "cpp_function(): function \"" + std::string(m_entry->name) + "\" takes " +
std::to_string(args) + " arguments, but " + std::to_string(m_entry->args.size()) + std::to_string(args) + " arguments, but " + std::to_string(m_entry->args.size()) +
" pybind11::arg entries were specified!"); " pybind11::arg entries were specified!");
...@@ -555,7 +554,7 @@ protected: ...@@ -555,7 +554,7 @@ protected:
}); });
m_ptr = PyCFunction_New(m_entry->def, entry_capsule.ptr()); m_ptr = PyCFunction_New(m_entry->def, entry_capsule.ptr());
if (!m_ptr) if (!m_ptr)
throw std::runtime_error("cpp_function::cpp_function(): Could not allocate function object"); pybind11_fail("cpp_function::cpp_function(): Could not allocate function object");
} else { } else {
/* Append at the end of the overload chain */ /* Append at the end of the overload chain */
m_ptr = m_entry->sibling; m_ptr = m_entry->sibling;
...@@ -597,7 +596,7 @@ protected: ...@@ -597,7 +596,7 @@ protected:
m_ptr = PyMethod_New(m_ptr, nullptr, entry->class_); m_ptr = PyMethod_New(m_ptr, nullptr, entry->class_);
#endif #endif
if (!m_ptr) if (!m_ptr)
throw std::runtime_error("cpp_function::cpp_function(): Could not allocate instance method object"); pybind11_fail("cpp_function::cpp_function(): Could not allocate instance method object");
Py_DECREF(func); Py_DECREF(func);
} }
} }
...@@ -621,7 +620,7 @@ public: ...@@ -621,7 +620,7 @@ public:
m_ptr = Py_InitModule3(name, nullptr, doc); m_ptr = Py_InitModule3(name, nullptr, doc);
#endif #endif
if (m_ptr == nullptr) if (m_ptr == nullptr)
throw std::runtime_error("Internal error in module::module()"); pybind11_fail("Internal error in module::module()");
inc_ref(); inc_ref();
} }
...@@ -647,7 +646,7 @@ public: ...@@ -647,7 +646,7 @@ public:
static module import(const char *name) { static module import(const char *name) {
PyObject *obj = PyImport_ImportModule(name); PyObject *obj = PyImport_ImportModule(name);
if (!obj) if (!obj)
throw std::runtime_error("Module \"" + std::string(name) + "\" not found!"); pybind11_fail("Module \"" + std::string(name) + "\" not found!");
return module(obj, false); return module(obj, false);
} }
}; };
...@@ -668,7 +667,7 @@ public: ...@@ -668,7 +667,7 @@ public:
auto type = (PyHeapTypeObject*) type_holder.ptr(); auto type = (PyHeapTypeObject*) type_holder.ptr();
if (!type_holder || !name) if (!type_holder || !name)
throw std::runtime_error("generic_type: unable to create type object!"); pybind11_fail("generic_type: unable to create type object!");
/* Register supplemental type information in C++ dict */ /* Register supplemental type information in C++ dict */
auto &internals = get_internals(); auto &internals = get_internals();
...@@ -732,7 +731,7 @@ public: ...@@ -732,7 +731,7 @@ public:
} }
if (PyType_Ready(&type->ht_type) < 0) if (PyType_Ready(&type->ht_type) < 0)
throw std::runtime_error("generic_type: PyType_Ready failed!"); pybind11_fail("generic_type: PyType_Ready failed!");
m_ptr = type_holder.ptr(); m_ptr = type_holder.ptr();
...@@ -756,7 +755,7 @@ protected: ...@@ -756,7 +755,7 @@ protected:
object type_holder(PyType_Type.tp_alloc(&PyType_Type, 0), false); object type_holder(PyType_Type.tp_alloc(&PyType_Type, 0), false);
object name(PYBIND11_FROM_STRING(name_.c_str()), false); object name(PYBIND11_FROM_STRING(name_.c_str()), false);
if (!type_holder || !name) if (!type_holder || !name)
throw std::runtime_error("generic_type::metaclass(): unable to create type object!"); pybind11_fail("generic_type::metaclass(): unable to create type object!");
auto type = (PyHeapTypeObject*) type_holder.ptr(); auto type = (PyHeapTypeObject*) type_holder.ptr();
type->ht_name = name.release(); type->ht_name = name.release();
...@@ -767,7 +766,7 @@ protected: ...@@ -767,7 +766,7 @@ protected:
~Py_TPFLAGS_HAVE_GC; ~Py_TPFLAGS_HAVE_GC;
if (PyType_Ready(&type->ht_type) < 0) if (PyType_Ready(&type->ht_type) < 0)
throw std::runtime_error("generic_type::metaclass(): PyType_Ready failed!"); pybind11_fail("generic_type::metaclass(): PyType_Ready failed!");
ob_type = (PyTypeObject *) type_holder.release(); ob_type = (PyTypeObject *) type_holder.release();
} }
...@@ -798,7 +797,7 @@ protected: ...@@ -798,7 +797,7 @@ protected:
auto &registered_instances = detail::get_internals().registered_instances; auto &registered_instances = detail::get_internals().registered_instances;
auto it = registered_instances.find(self->value); auto it = registered_instances.find(self->value);
if (it == registered_instances.end()) if (it == registered_instances.end())
throw std::runtime_error("generic_type::dealloc(): Tried to deallocate unregistered instance!"); pybind11_fail("generic_type::dealloc(): Tried to deallocate unregistered instance!");
registered_instances.erase(it); registered_instances.erase(it);
} }
Py_XDECREF(self->parent); Py_XDECREF(self->parent);
...@@ -1096,14 +1095,12 @@ PYBIND11_NOINLINE inline void keep_alive_impl(int Nurse, int Patient, PyObject * ...@@ -1096,14 +1095,12 @@ PYBIND11_NOINLINE inline void keep_alive_impl(int Nurse, int Patient, PyObject *
handle patient(Patient > 0 ? PyTuple_GetItem(arg, Patient - 1) : ret); handle patient(Patient > 0 ? PyTuple_GetItem(arg, Patient - 1) : ret);
if (!nurse || !patient) if (!nurse || !patient)
throw std::runtime_error("Could not activate keep_alive!"); pybind11_fail("Could not activate keep_alive!");
cpp_function disable_lifesupport( cpp_function disable_lifesupport(
[patient](handle weakref) { patient.dec_ref(); weakref.dec_ref(); }); [patient](handle weakref) { patient.dec_ref(); weakref.dec_ref(); });
weakref wr(nurse, disable_lifesupport); weakref wr(nurse, disable_lifesupport);
if (!wr)
throw std::runtime_error("Could not allocate weak reference!");
patient.inc_ref(); /* reference patient and leak the weak reference */ patient.inc_ref(); /* reference patient and leak the weak reference */
(void) wr.release(); (void) wr.release();
...@@ -1138,7 +1135,7 @@ template <typename InputType, typename OutputType> void implicitly_convertible() ...@@ -1138,7 +1135,7 @@ template <typename InputType, typename OutputType> void implicitly_convertible()
auto & registered_types = detail::get_internals().registered_types_cpp; auto & registered_types = detail::get_internals().registered_types_cpp;
auto it = registered_types.find(&typeid(OutputType)); auto it = registered_types.find(&typeid(OutputType));
if (it == registered_types.end()) if (it == registered_types.end())
throw std::runtime_error("implicitly_convertible: Unable to find type " + type_id<OutputType>()); pybind11_fail("implicitly_convertible: Unable to find type " + type_id<OutputType>());
((detail::type_info *) it->second)->implicit_conversions.push_back(implicit_caster); ((detail::type_info *) it->second)->implicit_conversions.push_back(implicit_caster);
} }
...@@ -1196,7 +1193,7 @@ inline function get_overload(const void *this_ptr, const char *name) { ...@@ -1196,7 +1193,7 @@ inline function get_overload(const void *this_ptr, const char *name) {
#define PYBIND11_OVERLOAD_PURE(ret_type, class_name, name, ...) \ #define PYBIND11_OVERLOAD_PURE(ret_type, class_name, name, ...) \
PYBIND11_OVERLOAD_INT(ret_type, class_name, name, __VA_ARGS__) \ PYBIND11_OVERLOAD_INT(ret_type, class_name, name, __VA_ARGS__) \
throw std::runtime_error("Tried to call pure virtual function \"" #name "\""); pybind11::pybind11_fail("Tried to call pure virtual function \"" #name "\"");
NAMESPACE_END(pybind11) NAMESPACE_END(pybind11)
......
...@@ -14,7 +14,6 @@ ...@@ -14,7 +14,6 @@
#include <set> #include <set>
#include <iostream> #include <iostream>
#if defined(_MSC_VER) #if defined(_MSC_VER)
#pragma warning(push) #pragma warning(push)
#pragma warning(disable: 4127) // warning C4127: Conditional expression is constant #pragma warning(disable: 4127) // warning C4127: Conditional expression is constant
...@@ -28,14 +27,14 @@ template <typename Value, typename Alloc> struct type_caster<std::vector<Value, ...@@ -28,14 +27,14 @@ template <typename Value, typename Alloc> struct type_caster<std::vector<Value,
typedef type_caster<Value> value_conv; typedef type_caster<Value> value_conv;
public: public:
bool load(PyObject *src, bool convert) { bool load(PyObject *src, bool convert) {
if (!PyList_Check(src)) list l(src, true);
if (!l.check())
return false; return false;
size_t size = (size_t) PyList_GET_SIZE(src); value.reserve(l.size());
value.reserve(size);
value.clear(); value.clear();
value_conv conv; value_conv conv;
for (size_t i=0; i<size; ++i) { for (auto it : l) {
if (!conv.load(PyList_GetItem(src, (ssize_t) i), convert)) if (!conv.load(it.ptr(), convert))
return false; return false;
value.push_back((Value) conv); value.push_back((Value) conv);
} }
...@@ -43,17 +42,15 @@ public: ...@@ -43,17 +42,15 @@ public:
} }
static PyObject *cast(const type &src, return_value_policy policy, PyObject *parent) { static PyObject *cast(const type &src, return_value_policy policy, PyObject *parent) {
object list(PyList_New(src.size()), false); list l(src.size());
if (!list)
return nullptr;
size_t index = 0; size_t index = 0;
for (auto const &value: src) { for (auto const &value: src) {
object value_ (value_conv::cast(value, policy, parent), false); object value_(value_conv::cast(value, policy, parent), false);
if (!value_) if (!value_)
return nullptr; return nullptr;
PyList_SET_ITEM(list.ptr(), index++, value_.release()); // steals a reference PyList_SET_ITEM(l.ptr(), index++, value_.release()); // steals a reference
} }
return list.release(); return l.release();
} }
PYBIND11_TYPE_CASTER(type, _("list<") + value_conv::name() + _(">")); PYBIND11_TYPE_CASTER(type, _("list<") + value_conv::name() + _(">"));
}; };
...@@ -68,8 +65,8 @@ public: ...@@ -68,8 +65,8 @@ public:
return false; return false;
value.clear(); value.clear();
key_conv conv; key_conv conv;
for (const object &o: s) { for (auto entry : s) {
if (!conv.load((PyObject *) o.ptr(), convert)) if (!conv.load(entry.ptr(), convert))
return false; return false;
value.insert((Key) conv); value.insert((Key) conv);
} }
...@@ -77,15 +74,13 @@ public: ...@@ -77,15 +74,13 @@ public:
} }
static PyObject *cast(const type &src, return_value_policy policy, PyObject *parent) { static PyObject *cast(const type &src, return_value_policy policy, PyObject *parent) {
object set(PySet_New(nullptr), false); pybind11::set s;
if (!set)
return nullptr;
for (auto const &value: src) { for (auto const &value: src) {
object value_(key_conv::cast(value, policy, parent), false); object value_(key_conv::cast(value, policy, parent), false);
if (!value_ || PySet_Add(set.ptr(), value_.ptr()) != 0) if (!value_ || !s.add(value))
return nullptr; return nullptr;
} }
return set.release(); return s.release();
} }
PYBIND11_TYPE_CASTER(type, _("set<") + key_conv::name() + _(">")); PYBIND11_TYPE_CASTER(type, _("set<") + key_conv::name() + _(">"));
}; };
...@@ -97,16 +92,15 @@ public: ...@@ -97,16 +92,15 @@ public:
typedef type_caster<Value> value_conv; typedef type_caster<Value> value_conv;
bool load(PyObject *src, bool convert) { bool load(PyObject *src, bool convert) {
if (!PyDict_Check(src)) dict d(src, true);
if (!d.check())
return false; return false;
value.clear();
PyObject *key_, *value_;
ssize_t pos = 0;
key_conv kconv; key_conv kconv;
value_conv vconv; value_conv vconv;
while (PyDict_Next(src, &pos, &key_, &value_)) { value.clear();
if (!kconv.load(key_, convert) || !vconv.load(value_, convert)) for (auto it : d) {
if (!kconv.load(it.first.ptr(), convert) ||
!vconv.load(it.second.ptr(), convert))
return false; return false;
value[(Key) kconv] = (Value) vconv; value[(Key) kconv] = (Value) vconv;
} }
...@@ -114,16 +108,15 @@ public: ...@@ -114,16 +108,15 @@ public:
} }
static PyObject *cast(const type &src, return_value_policy policy, PyObject *parent) { static PyObject *cast(const type &src, return_value_policy policy, PyObject *parent) {
object dict(PyDict_New(), false); dict d;
if (!dict)
return nullptr;
for (auto const &kv: src) { for (auto const &kv: src) {
object key(key_conv::cast(kv.first, policy, parent), false); object key(key_conv::cast(kv.first, policy, parent), false);
object value(value_conv::cast(kv.second, policy, parent), false); object value(value_conv::cast(kv.second, policy, parent), false);
if (!key || !value || PyDict_SetItem(dict.ptr(), key.ptr(), value.ptr()) != 0) if (!key || !value)
return nullptr; return nullptr;
d[key] = value;
} }
return dict.release(); return d.release();
} }
PYBIND11_TYPE_CASTER(type, _("dict<") + key_conv::name() + _(", ") + value_conv::name() + _(">")); PYBIND11_TYPE_CASTER(type, _("dict<") + key_conv::name() + _(", ") + value_conv::name() + _(">"));
...@@ -131,7 +124,10 @@ public: ...@@ -131,7 +124,10 @@ public:
NAMESPACE_END(detail) NAMESPACE_END(detail)
inline std::ostream &operator<<(std::ostream &os, const object &obj) { os << (std::string) obj.str(); return os; } inline std::ostream &operator<<(std::ostream &os, const handle &obj) {
os << (std::string) obj.str();
return os;
}
NAMESPACE_END(pybind11) NAMESPACE_END(pybind11)
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
#include <cstdio> #include <cstdio>
#include <cstdlib> #include <cstdlib>
#if defined(__GNUG__) #if defined(__GNUG__)
#include <cxxabi.h> #include <cxxabi.h>
#endif #endif
...@@ -26,7 +27,7 @@ inline void erase_all(std::string &string, const std::string &search) { ...@@ -26,7 +27,7 @@ inline void erase_all(std::string &string, const std::string &search) {
} }
} }
inline void clean_type_id(std::string &name) { PYBIND11_NOINLINE inline void clean_type_id(std::string &name) {
#if defined(__GNUG__) #if defined(__GNUG__)
int status = 0; int status = 0;
std::unique_ptr<char, void (*)(void *)> res { std::unique_ptr<char, void (*)(void *)> res {
......
...@@ -17,6 +17,7 @@ setup( ...@@ -17,6 +17,7 @@ setup(
headers=[ headers=[
'include/pybind11/cast.h', 'include/pybind11/cast.h',
'include/pybind11/complex.h', 'include/pybind11/complex.h',
'include/pybind11/descr.h',
'include/pybind11/numpy.h', 'include/pybind11/numpy.h',
'include/pybind11/pybind11.h', 'include/pybind11/pybind11.h',
'include/pybind11/stl.h', 'include/pybind11/stl.h',
...@@ -57,11 +58,10 @@ C++11-compatible compilers are widely available, this heavy machinery has ...@@ -57,11 +58,10 @@ C++11-compatible compilers are widely available, this heavy machinery has
become an excessively large and unnecessary dependency. become an excessively large and unnecessary dependency.
Think of this library as a tiny self-contained version of Boost.Python with Think of this library as a tiny self-contained version of Boost.Python with
everything stripped away that isn't relevant for binding generation. The whole everything stripped away that isn't relevant for binding generation. The core
codebase requires less than 3000 lines of code and only depends on Python (2.7 header files only require ~2.5K lines of code and depend on Python (2.7 or 3.x)
or 3.x) and the C++ standard library. This compact implementation was and the C++ standard library. This compact implementation was possible thanks
possible thanks to some of the new C++11 language features (tuples, lambda to some of the new C++11 language features (specifically: tuples, lambda
functions and variadic templates). Since its creation, this library has functions and variadic templates). Since its creation, this library has grown
grown beyond Boost.Python in many ways, leading to dramatically simpler binding beyond Boost.Python in many ways, leading to dramatically simpler binding code
code in many common situations.""", in many common situations.""")
)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment