Skip to content

Commit 2447088

Browse files
committed
Merge branches 'eigen-numpy-referencing', 'unicode-fixes-and-docs' and 'args-kwargs-posargs-mixing'
4 parents 27e8365 + c0ac6dd + 1dcbc76 + a3ac042 commit 2447088

File tree

14 files changed

+516
-327
lines changed

14 files changed

+516
-327
lines changed

docs/advanced/cast/eigen.rst

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,51 @@ consideration is required: the created numpy array will have the required
223223
stride that allows numpy to properly interpret the array, whatever its storage
224224
order.
225225

226+
Failing rather than copying
227+
===========================
228+
229+
The default behaviour when binding ``Eigen::Ref<const MatrixType>`` eigen
230+
references is to copy matrix values when passed a numpy array that does not
231+
conform to the element type of ``MatrixType`` or does not have a compatible
232+
stride layout. If you want to explicitly avoid copying in such a case,
233+
pybind11 provides a special ``py::EigenNoCopyRef<const Matrix>`` class that
234+
works exactly like ``Eigen::Ref<const Matrix>``, but lets pybind11 know that it
235+
should fail to accept incompatible data rather than performing a conversion.
236+
237+
In order to use this in binding code, it is typically required to provide an
238+
intermediate lambda function that accepts the special class then simply calls
239+
the actual ``Eigen::Ref<const Matrix>``-accepting function. The following example
240+
shows how to do this for both a function and instance method:
241+
242+
.. code-block:: cpp
243+
244+
// The method and function to be bound:
245+
class MyClass {
246+
// ...
247+
double some_method(const Eigen::Ref<const MatrixXd> &matrix) { /* ... */ }
248+
};
249+
float some_function(const Eigen::Ref<const Matrix4f> &matrix) { /* ... */ }
250+
251+
// The associated binding code:
252+
py::class_<MyClass>(m, "MyClass")
253+
// ... other class definitions
254+
.def("some_method", [](MyClass &cl, py::EigenNoCopyRef<const MatrixXd> &matrix) {
255+
cl.some_method(matrix);
256+
});
257+
m.def("some_function", [](py::EigenNoCopyRef<const Matrix4f> &matrix) {
258+
return some_function(matrix);
259+
});
260+
261+
With the above binding code, attempting to call the the ``some_method(m)``
262+
method on a ``MyClass`` object, or attempting to call ``some_function(m)`` will
263+
raise a ``RuntimeError`` rather than making a temporary copy of the array.
264+
This wrapper class may only be used with non-mutable references (generally,
265+
references with a ``const`` matrix type): mutable references such as
266+
``Eigen::Ref<Eigen::MatrixXd>`` are never copied.
267+
268+
pybind11 also provides a ``py::EigenNoCopyDRef`` which is the no-copying
269+
version of ``py::EigenDRef``.
270+
226271
Vectors versus column/row matrices
227272
==================================
228273

docs/advanced/cast/overview.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,14 +94,26 @@ as arguments and return values, refer to the section on binding :ref:`classes`.
9494
+------------------------------------+---------------------------+-------------------------------+
9595
| ``char`` | Character literal | :file:`pybind11/pybind11.h` |
9696
+------------------------------------+---------------------------+-------------------------------+
97+
| ``char16_t`` | UTF-16 character literal | :file:`pybind11/pybind11.h` |
98+
+------------------------------------+---------------------------+-------------------------------+
99+
| ``char32_t`` | UTF-32 character literal | :file:`pybind11/pybind11.h` |
100+
+------------------------------------+---------------------------+-------------------------------+
97101
| ``wchar_t`` | Wide character literal | :file:`pybind11/pybind11.h` |
98102
+------------------------------------+---------------------------+-------------------------------+
99103
| ``const char *`` | UTF-8 string literal | :file:`pybind11/pybind11.h` |
100104
+------------------------------------+---------------------------+-------------------------------+
105+
| ``const char16_t *`` | UTF-16 string literal | :file:`pybind11/pybind11.h` |
106+
+------------------------------------+---------------------------+-------------------------------+
107+
| ``const char32_t *`` | UTF-32 string literal | :file:`pybind11/pybind11.h` |
108+
+------------------------------------+---------------------------+-------------------------------+
101109
| ``const wchar_t *`` | Wide string literal | :file:`pybind11/pybind11.h` |
102110
+------------------------------------+---------------------------+-------------------------------+
103111
| ``std::string`` | STL dynamic UTF-8 string | :file:`pybind11/pybind11.h` |
104112
+------------------------------------+---------------------------+-------------------------------+
113+
| ``std::u16string`` | STL dynamic UTF-16 string | :file:`pybind11/pybind11.h` |
114+
+------------------------------------+---------------------------+-------------------------------+
115+
| ``std::u32string`` | STL dynamic UTF-32 string | :file:`pybind11/pybind11.h` |
116+
+------------------------------------+---------------------------+-------------------------------+
105117
| ``std::wstring`` | STL dynamic wide string | :file:`pybind11/pybind11.h` |
106118
+------------------------------------+---------------------------+-------------------------------+
107119
| ``std::pair<T1, T2>`` | Pair of two custom types | :file:`pybind11/pybind11.h` |

include/pybind11/attr.h

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@ struct undefined_t;
6464
template <op_id id, op_type ot, typename L = undefined_t, typename R = undefined_t> struct op_;
6565
template <typename... Args> struct init;
6666
template <typename... Args> struct init_alias;
67-
inline void keep_alive_impl(size_t Nurse, size_t Patient, function_arguments args, handle ret);
67+
struct function_call;
68+
inline void keep_alive_impl(size_t Nurse, size_t Patient, function_call &call, handle ret);
6869

6970
/// Internal data structure which holds metadata about a keyword argument
7071
struct argument_record {
@@ -95,7 +96,7 @@ struct function_record {
9596
std::vector<argument_record> args;
9697

9798
/// Pointer to lambda function which converts arguments and performs the actual call
98-
handle (*impl) (function_record *, function_arguments, handle) = nullptr;
99+
handle (*impl) (function_call &) = nullptr;
99100

100101
/// Storage for the wrapped function pointer and captured data, if any
101102
void *data[3] = { };
@@ -204,6 +205,22 @@ struct type_record {
204205
}
205206
};
206207

208+
/// Internal data associated with a single function call
209+
struct function_call {
210+
function_call(const function_record &f, handle p) : func{f}, parent(p) {
211+
args.reserve(f.nargs);
212+
}
213+
214+
/// The function data:
215+
const function_record &func;
216+
217+
/// Arguments passed to the function:
218+
std::vector<handle> args;
219+
220+
/// The parent, if any
221+
handle parent;
222+
};
223+
207224
/**
208225
* Partial template specializations to process custom attributes provided to
209226
* cpp_function_ and class_. These are either used to initialize the respective
@@ -216,8 +233,8 @@ template <typename T> struct process_attribute_default {
216233
/// Default implementation: do nothing
217234
static void init(const T &, function_record *) { }
218235
static void init(const T &, type_record *) { }
219-
static void precall(function_arguments) { }
220-
static void postcall(function_arguments, handle) { }
236+
static void precall(function_call &) { }
237+
static void postcall(function_call &, handle) { }
221238
};
222239

223240
/// Process an attribute specifying the function's name
@@ -345,13 +362,13 @@ struct process_attribute<arithmetic> : process_attribute_default<arithmetic> {};
345362
*/
346363
template <size_t Nurse, size_t Patient> struct process_attribute<keep_alive<Nurse, Patient>> : public process_attribute_default<keep_alive<Nurse, Patient>> {
347364
template <size_t N = Nurse, size_t P = Patient, enable_if_t<N != 0 && P != 0, int> = 0>
348-
static void precall(function_arguments args) { keep_alive_impl(Nurse, Patient, args, handle()); }
365+
static void precall(function_call &call) { keep_alive_impl(Nurse, Patient, call, handle()); }
349366
template <size_t N = Nurse, size_t P = Patient, enable_if_t<N != 0 && P != 0, int> = 0>
350-
static void postcall(function_arguments, handle) { }
367+
static void postcall(function_call &, handle) { }
351368
template <size_t N = Nurse, size_t P = Patient, enable_if_t<N == 0 || P == 0, int> = 0>
352-
static void precall(function_arguments) { }
369+
static void precall(function_call &) { }
353370
template <size_t N = Nurse, size_t P = Patient, enable_if_t<N == 0 || P == 0, int> = 0>
354-
static void postcall(function_arguments args, handle ret) { keep_alive_impl(Nurse, Patient, args, ret); }
371+
static void postcall(function_call &call, handle ret) { keep_alive_impl(Nurse, Patient, call, ret); }
355372
};
356373

357374
/// Recursively iterate over variadic template arguments
@@ -364,12 +381,12 @@ template <typename... Args> struct process_attributes {
364381
int unused[] = { 0, (process_attribute<typename std::decay<Args>::type>::init(args, r), 0) ... };
365382
ignore_unused(unused);
366383
}
367-
static void precall(function_arguments fn_args) {
368-
int unused[] = { 0, (process_attribute<typename std::decay<Args>::type>::precall(fn_args), 0) ... };
384+
static void precall(function_call &call) {
385+
int unused[] = { 0, (process_attribute<typename std::decay<Args>::type>::precall(call), 0) ... };
369386
ignore_unused(unused);
370387
}
371-
static void postcall(function_arguments fn_args, handle fn_ret) {
372-
int unused[] = { 0, (process_attribute<typename std::decay<Args>::type>::postcall(fn_args, fn_ret), 0) ... };
388+
static void postcall(function_call &call, handle fn_ret) {
389+
int unused[] = { 0, (process_attribute<typename std::decay<Args>::type>::postcall(call, fn_ret), 0) ... };
373390
ignore_unused(unused);
374391
}
375392
};

include/pybind11/cast.h

Lines changed: 69 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -462,8 +462,15 @@ template <typename type> class type_caster<std::reference_wrapper<type>> : publi
462462
template <typename _T> using cast_op_type = pybind11::detail::cast_op_type<_T>
463463

464464

465+
template <typename CharT> using is_std_char_type = any_of<
466+
std::is_same<CharT, char>, /* std::string */
467+
std::is_same<CharT, char16_t>, /* std::u16string */
468+
std::is_same<CharT, char32_t>, /* std::u32string */
469+
std::is_same<CharT, wchar_t> /* std::wstring */
470+
>;
471+
465472
template <typename T>
466-
struct type_caster<T, enable_if_t<std::is_arithmetic<T>::value>> {
473+
struct type_caster<T, enable_if_t<std::is_arithmetic<T>::value && !is_std_char_type<T>::value>> {
467474
typedef typename std::conditional<sizeof(T) <= sizeof(long), long, long long>::type _py_type_0;
468475
typedef typename std::conditional<std::is_signed<T>::value, _py_type_0, typename std::make_unsigned<_py_type_0>::type>::type _py_type_1;
469476
typedef typename std::conditional<std::is_floating_point<T>::value, double, _py_type_1>::type py_type;
@@ -604,49 +611,22 @@ template <> class type_caster<bool> {
604611
PYBIND11_TYPE_CASTER(bool, _("bool"));
605612
};
606613

607-
template <> class type_caster<std::string> {
608-
public:
609-
bool load(handle src, bool) {
610-
object temp;
611-
handle load_src = src;
612-
if (!src) {
613-
return false;
614-
} else if (PyUnicode_Check(load_src.ptr())) {
615-
temp = reinterpret_steal<object>(PyUnicode_AsUTF8String(load_src.ptr()));
616-
if (!temp) { PyErr_Clear(); return false; } // UnicodeEncodeError
617-
load_src = temp;
618-
}
619-
char *buffer;
620-
ssize_t length;
621-
int err = PYBIND11_BYTES_AS_STRING_AND_SIZE(load_src.ptr(), &buffer, &length);
622-
if (err == -1) { PyErr_Clear(); return false; } // TypeError
623-
value = std::string(buffer, (size_t) length);
624-
success = true;
625-
return true;
626-
}
614+
// Helper class for UTF-{8,16,32} strings:
615+
template <typename CharT, class Traits, class Allocator>
616+
struct type_caster<std::basic_string<CharT, Traits, Allocator>, enable_if_t<is_std_char_type<CharT>::value>> {
617+
static constexpr unsigned int UTF_N =
618+
std::is_same<CharT, char>::value ? 8 :
619+
std::is_same<CharT, char16_t>::value ? 16 :
620+
std::is_same<CharT, char32_t>::value ? 32 :
621+
(sizeof(CharT) == 2 ? 16 : 32); /* std::wstring is UTF-16 on Windows, UTF-32 everywhere else */
627622

628-
static handle cast(const std::string &src, return_value_policy /* policy */, handle /* parent */) {
629-
return PyUnicode_FromStringAndSize(src.c_str(), (ssize_t) src.length());
630-
}
623+
// C++ only requires char/char16_t/char32_t to be at least 8/16/32 bits, but Python's encoding
624+
// assumes exactly 1/2/4 bytes:
625+
static_assert(sizeof(CharT) == UTF_N / 8,
626+
"Internal error: string type_caster requires 1/2/4-sized character types");
631627

632-
PYBIND11_TYPE_CASTER(std::string, _(PYBIND11_STRING_NAME));
633-
protected:
634-
bool success = false;
635-
};
628+
using StringType = std::basic_string<CharT, Traits, Allocator>;
636629

637-
template <typename type, typename deleter> class type_caster<std::unique_ptr<type, deleter>> {
638-
public:
639-
static handle cast(std::unique_ptr<type, deleter> &&src, return_value_policy policy, handle parent) {
640-
handle result = type_caster_base<type>::cast(src.get(), policy, parent);
641-
if (result)
642-
src.release();
643-
return result;
644-
}
645-
static PYBIND11_DESCR name() { return type_caster_base<type>::name(); }
646-
};
647-
648-
template <> class type_caster<std::wstring> {
649-
public:
650630
bool load(handle src, bool) {
651631
object temp;
652632
handle load_src = src;
@@ -657,78 +637,62 @@ template <> class type_caster<std::wstring> {
657637
if (!temp) { PyErr_Clear(); return false; }
658638
load_src = temp;
659639
}
660-
wchar_t *buffer = nullptr;
661-
ssize_t length = -1;
662-
#if PY_MAJOR_VERSION >= 3
663-
buffer = PyUnicode_AsWideCharString(load_src.ptr(), &length);
664-
#else
665-
temp = reinterpret_steal<object>(PyUnicode_AsEncodedString(
666-
load_src.ptr(), sizeof(wchar_t) == sizeof(short)
667-
? "utf16" : "utf32", nullptr));
668-
669-
if (temp) {
670-
int err = PYBIND11_BYTES_AS_STRING_AND_SIZE(temp.ptr(), (char **) &buffer, &length);
671-
if (err == -1) { buffer = nullptr; } // TypeError
672-
length = length / (ssize_t) sizeof(wchar_t) - 1; ++buffer; // Skip BOM
673-
}
674-
#endif
675-
if (!buffer) { PyErr_Clear(); return false; }
676-
value = std::wstring(buffer, (size_t) length);
640+
641+
object utfNbytes = reinterpret_steal<object>(PyUnicode_AsEncodedString(
642+
load_src.ptr(),
643+
UTF_N == 8 ? "utf8" : UTF_N == 16 ? "utf16" : "utf32",
644+
nullptr));
645+
if (!utfNbytes) { PyErr_Clear(); return false; }
646+
647+
const CharT *buffer = reinterpret_cast<const CharT *>(PYBIND11_BYTES_AS_STRING(utfNbytes.ptr()));
648+
size_t length = (size_t) PYBIND11_BYTES_SIZE(utfNbytes.ptr()) / sizeof(CharT);
649+
if (UTF_N > 8) { buffer++; length--; } // Skip BOM for UTF-16/32
650+
value = StringType(buffer, length);
677651
success = true;
678652
return true;
679653
}
680654

681-
static handle cast(const std::wstring &src, return_value_policy /* policy */, handle /* parent */) {
682-
return PyUnicode_FromWideChar(src.c_str(), (ssize_t) src.length());
655+
static handle cast(const StringType &src, return_value_policy /* policy */, handle /* parent */) {
656+
const char *buffer = reinterpret_cast<const char *>(src.c_str());
657+
ssize_t nbytes = ssize_t(src.size() * sizeof(CharT));
658+
handle s = PyUnicode_Decode(buffer, nbytes, UTF_N == 8 ? "utf8" : UTF_N == 16 ? "utf16" : "utf32", nullptr);
659+
if (!s) throw error_already_set();
660+
return s;
683661
}
684662

685-
PYBIND11_TYPE_CASTER(std::wstring, _(PYBIND11_STRING_NAME));
663+
PYBIND11_TYPE_CASTER(StringType, _(PYBIND11_STRING_NAME));
686664
protected:
687665
bool success = false;
688666
};
689667

690-
template <> class type_caster<char> : public type_caster<std::string> {
668+
template <typename CharT> struct type_caster<CharT, enable_if_t<is_std_char_type<CharT>::value>>
669+
: type_caster<std::basic_string<CharT>> {
670+
using StringType = std::basic_string<CharT>;
671+
using StringCaster = type_caster<StringType>;
672+
using StringCaster::success;
673+
using StringCaster::value;
691674
public:
692675
bool load(handle src, bool convert) {
693676
if (src.is_none()) return true;
694-
return type_caster<std::string>::load(src, convert);
677+
return StringCaster::load(src, convert);
695678
}
696679

697-
static handle cast(const char *src, return_value_policy /* policy */, handle /* parent */) {
680+
static handle cast(const CharT *src, return_value_policy policy, handle parent) {
698681
if (src == nullptr) return none().inc_ref();
699-
return PyUnicode_FromString(src);
700-
}
701-
702-
static handle cast(char src, return_value_policy /* policy */, handle /* parent */) {
703-
char str[2] = { src, '\0' };
704-
return PyUnicode_DecodeLatin1(str, 1, nullptr);
705-
}
706-
707-
operator char*() { return success ? (char *) value.c_str() : nullptr; }
708-
operator char&() { return value[0]; }
709-
710-
static PYBIND11_DESCR name() { return type_descr(_(PYBIND11_STRING_NAME)); }
711-
};
712-
713-
template <> class type_caster<wchar_t> : public type_caster<std::wstring> {
714-
public:
715-
bool load(handle src, bool convert) {
716-
if (src.is_none()) return true;
717-
return type_caster<std::wstring>::load(src, convert);
682+
return StringCaster::cast(StringType(src), policy, parent);
718683
}
719684

720-
static handle cast(const wchar_t *src, return_value_policy /* policy */, handle /* parent */) {
721-
if (src == nullptr) return none().inc_ref();
722-
return PyUnicode_FromWideChar(src, (ssize_t) wcslen(src));
723-
}
724-
725-
static handle cast(wchar_t src, return_value_policy /* policy */, handle /* parent */) {
726-
wchar_t wstr[2] = { src, L'\0' };
727-
return PyUnicode_FromWideChar(wstr, 1);
685+
static handle cast(CharT src, return_value_policy policy, handle parent) {
686+
if (std::is_same<char, CharT>::value) {
687+
handle s = PyUnicode_DecodeLatin1((const char *) &src, 1, nullptr);
688+
if (!s) throw error_already_set();
689+
return s;
690+
}
691+
return StringCaster::cast(StringType(1, src), policy, parent);
728692
}
729693

730-
operator wchar_t*() { return success ? (wchar_t *) value.c_str() : nullptr; }
731-
operator wchar_t&() { return value[0]; }
694+
operator CharT*() { return success ? (CharT *) value.c_str() : nullptr; }
695+
operator CharT&() { return value[0]; }
732696

733697
static PYBIND11_DESCR name() { return type_descr(_(PYBIND11_STRING_NAME)); }
734698
};
@@ -835,6 +799,17 @@ template <typename... Tuple> class type_caster<std::tuple<Tuple...>> {
835799
std::tuple<make_caster<Tuple>...> value;
836800
};
837801

802+
template <typename type, typename deleter> class type_caster<std::unique_ptr<type, deleter>> {
803+
public:
804+
static handle cast(std::unique_ptr<type, deleter> &&src, return_value_policy policy, handle parent) {
805+
handle result = type_caster_base<type>::cast(src.get(), policy, parent);
806+
if (result)
807+
src.release();
808+
return result;
809+
}
810+
static PYBIND11_DESCR name() { return type_caster_base<type>::name(); }
811+
};
812+
838813
/// Type caster for holder types like std::shared_ptr, etc.
839814
template <typename type, typename holder_type> class type_caster_holder : public type_caster_base<type> {
840815
public:
@@ -1231,12 +1206,11 @@ NAMESPACE_BEGIN(detail)
12311206
// forward declaration
12321207
struct function_record;
12331208

1234-
using function_arguments = const std::vector<handle> &;
1235-
12361209
/// Helper class which loads arguments for C++ functions called from Python
12371210
template <typename... Args>
12381211
class argument_loader {
12391212
using indices = make_index_sequence<sizeof...(Args)>;
1213+
using function_arguments = const std::vector<handle> &;
12401214

12411215
template <typename Arg> using argument_is_args = std::is_same<intrinsic_t<Arg>, args>;
12421216
template <typename Arg> using argument_is_kwargs = std::is_same<intrinsic_t<Arg>, kwargs>;

0 commit comments

Comments
 (0)