Skip to content

Commit 9a0c96d

Browse files
authored
feat: py::prepend tag (#1131)
* feat: add a priority overload with py::prepend * doc: fix wording as suggested by rwgk * feat: add get_pointer * refactor: is_prepended -> prepend (internal) * docs: suggestion from @wjakob * tests: add test covering get_pointer/set_pointer
1 parent f537093 commit 9a0c96d

File tree

9 files changed

+105
-14
lines changed

9 files changed

+105
-14
lines changed

docs/advanced/functions.rst

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -540,11 +540,13 @@ an explicit ``py::arg().noconvert()`` attribute in the function definition).
540540
If the second pass also fails a ``TypeError`` is raised.
541541

542542
Within each pass, overloads are tried in the order they were registered with
543-
pybind11.
543+
pybind11. If the ``py::prepend()`` tag is added to the definition, a function
544+
can be placed at the beginning of the overload sequence instead, allowing user
545+
overloads to proceed built in functions.
544546

545547
What this means in practice is that pybind11 will prefer any overload that does
546-
not require conversion of arguments to an overload that does, but otherwise prefers
547-
earlier-defined overloads to later-defined ones.
548+
not require conversion of arguments to an overload that does, but otherwise
549+
prefers earlier-defined overloads to later-defined ones.
548550

549551
.. note::
550552

@@ -553,3 +555,7 @@ earlier-defined overloads to later-defined ones.
553555
requiring one conversion over one requiring three, but only prioritizes
554556
overloads requiring no conversion at all to overloads that require
555557
conversion of at least one argument.
558+
559+
.. versionadded:: 2.6
560+
561+
The ``py::prepend()`` tag.

docs/changelog.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ See :ref:`upgrade-guide-2.6` for help upgrading to the new version.
3232
``py::type::of(h)``.
3333
`#2364 <https://github.com/pybind/pybind11/pull/2364>`_
3434

35+
* Added ``py::prepend()``, allowing a function to be placed at the beginning of
36+
the overload chain.
37+
`#1131 <https://github.com/pybind/pybind11/pull/1131>`_
3538

3639
* Perfect forwarding support for methods.
3740
`#2048 <https://github.com/pybind/pybind11/pull/2048>`_
@@ -136,6 +139,9 @@ Smaller or developer focused features:
136139
* ``py::vectorize`` is now supported on functions that return void.
137140
`#1969 <https://github.com/pybind/pybind11/pull/1969>`_
138141

142+
* ``py::capsule`` supports ``get_pointer`` and ``set_pointer``.
143+
`#1131 <https://github.com/pybind/pybind11/pull/1131>`_
144+
139145
* Bugfixes related to more extensive testing.
140146
`#2321 <https://github.com/pybind/pybind11/pull/2321>`_
141147

docs/upgrade.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ missing.
2626
The undocumented ``h.get_type()`` method has been deprecated and replaced by
2727
``py::type::of(h)``.
2828

29+
Enums now have a ``__str__`` method pre-defined; if you want to override it,
30+
the simplest fix is to add the new ``py::prepend()`` tag when defining
31+
``"__str__"``.
32+
2933
If ``__eq__`` defined but not ``__hash__``, ``__hash__`` is now set to
3034
``None``, as in normal CPython. You should add ``__hash__`` if you intended the
3135
class to be hashable, possibly using the new ``py::hash`` shortcut.

include/pybind11/attr.h

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ struct module_local { const bool value; constexpr module_local(bool v = true) :
7474
/// Annotation to mark enums as an arithmetic type
7575
struct arithmetic { };
7676

77+
/// Mark a function for addition at the beginning of the existing overload chain instead of the end
78+
struct prepend { };
79+
7780
/** \rst
7881
A call policy which places one or more guard variables (``Ts...``) around the function call.
7982
@@ -138,8 +141,8 @@ struct argument_record {
138141
struct function_record {
139142
function_record()
140143
: is_constructor(false), is_new_style_constructor(false), is_stateless(false),
141-
is_operator(false), is_method(false),
142-
has_args(false), has_kwargs(false), has_kw_only_args(false) { }
144+
is_operator(false), is_method(false), has_args(false),
145+
has_kwargs(false), has_kw_only_args(false), prepend(false) { }
143146

144147
/// Function name
145148
char *name = nullptr; /* why no C++ strings? They generate heavier code.. */
@@ -189,6 +192,9 @@ struct function_record {
189192
/// True once a 'py::kw_only' is encountered (any following args are keyword-only)
190193
bool has_kw_only_args : 1;
191194

195+
/// True if this function is to be inserted at the beginning of the overload resolution chain
196+
bool prepend : 1;
197+
192198
/// Number of arguments (including py::args and/or py::kwargs, if present)
193199
std::uint16_t nargs;
194200

@@ -477,6 +483,12 @@ struct process_attribute<module_local> : process_attribute_default<module_local>
477483
static void init(const module_local &l, type_record *r) { r->module_local = l.value; }
478484
};
479485

486+
/// Process a 'prepend' attribute, putting this at the beginning of the overload chain
487+
template <>
488+
struct process_attribute<prepend> : process_attribute_default<prepend> {
489+
static void init(const prepend &, function_record *r) { r->prepend = true; }
490+
};
491+
480492
/// Process an 'arithmetic' attribute for enums (does nothing here)
481493
template <>
482494
struct process_attribute<arithmetic> : process_attribute_default<arithmetic> {};

include/pybind11/pybind11.h

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -372,10 +372,9 @@ class cpp_function : public function {
372372
if (!m_ptr)
373373
pybind11_fail("cpp_function::cpp_function(): Could not allocate function object");
374374
} else {
375-
/* Append at the end of the overload chain */
375+
/* Append at the beginning or end of the overload chain */
376376
m_ptr = rec->sibling.ptr();
377377
inc_ref();
378-
chain_start = chain;
379378
if (chain->is_method != rec->is_method)
380379
pybind11_fail("overloading a method with both static and instance methods is not supported; "
381380
#if defined(NDEBUG)
@@ -385,9 +384,22 @@ class cpp_function : public function {
385384
std::string(pybind11::str(rec->scope.attr("__name__"))) + "." + std::string(rec->name) + signature
386385
#endif
387386
);
388-
while (chain->next)
389-
chain = chain->next;
390-
chain->next = rec;
387+
388+
if (rec->prepend) {
389+
// Beginning of chain; we need to replace the capsule's current head-of-the-chain
390+
// pointer with this one, then make this one point to the previous head of the
391+
// chain.
392+
chain_start = rec;
393+
rec->next = chain;
394+
auto rec_capsule = reinterpret_borrow<capsule>(((PyCFunctionObject *) m_ptr)->m_self);
395+
rec_capsule.set_pointer(rec);
396+
} else {
397+
// Or end of chain (normal behavior)
398+
chain_start = chain;
399+
while (chain->next)
400+
chain = chain->next;
401+
chain->next = rec;
402+
}
391403
}
392404

393405
std::string signatures;

include/pybind11/pytypes.h

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1236,12 +1236,24 @@ class capsule : public object {
12361236
}
12371237

12381238
template <typename T> operator T *() const {
1239+
return get_pointer<T>();
1240+
}
1241+
1242+
/// Get the pointer the capsule holds.
1243+
template<typename T = void>
1244+
T* get_pointer() const {
12391245
auto name = this->name();
1240-
T * result = static_cast<T *>(PyCapsule_GetPointer(m_ptr, name));
1246+
T *result = static_cast<T *>(PyCapsule_GetPointer(m_ptr, name));
12411247
if (!result) pybind11_fail("Unable to extract capsule contents!");
12421248
return result;
12431249
}
12441250

1251+
/// Replaces a capsule's pointer *without* calling the destructor on the existing one.
1252+
void set_pointer(const void *value) {
1253+
if (PyCapsule_SetPointer(m_ptr, const_cast<void *>(value)) != 0)
1254+
pybind11_fail("Could not set capsule pointer");
1255+
}
1256+
12451257
const char *name() const { return PyCapsule_GetName(m_ptr); }
12461258
};
12471259

tests/test_methods_and_attributes.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,12 @@ TEST_SUBMODULE(methods_and_attributes, m) {
284284
py::class_<MetaclassOverride>(m, "MetaclassOverride", py::metaclass((PyObject *) &PyType_Type))
285285
.def_property_readonly_static("readonly", [](py::object) { return 1; });
286286

287+
// test_overload_ordering
288+
m.def("overload_order", [](std::string) { return 1; });
289+
m.def("overload_order", [](std::string) { return 2; });
290+
m.def("overload_order", [](int) { return 3; });
291+
m.def("overload_order", [](int) { return 4; }, py::prepend{});
292+
287293
#if !defined(PYPY_VERSION)
288294
// test_dynamic_attributes
289295
class DynamicClass {

tests/test_methods_and_attributes.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,3 +438,25 @@ def test_ref_qualified():
438438
r.refQualified(17)
439439
assert r.value == 17
440440
assert r.constRefQualified(23) == 40
441+
442+
443+
def test_overload_ordering():
444+
'Check to see if the normal overload order (first defined) and prepend overload order works'
445+
assert m.overload_order("string") == 1
446+
assert m.overload_order(0) == 4
447+
448+
# Different for Python 2 vs. 3
449+
uni_name = type(u"").__name__
450+
451+
assert "1. overload_order(arg0: int) -> int" in m.overload_order.__doc__
452+
assert "2. overload_order(arg0: {}) -> int".format(uni_name) in m.overload_order.__doc__
453+
assert "3. overload_order(arg0: {}) -> int".format(uni_name) in m.overload_order.__doc__
454+
assert "4. overload_order(arg0: int) -> int" in m.overload_order.__doc__
455+
456+
with pytest.raises(TypeError) as err:
457+
m.overload_order(1.1)
458+
459+
assert "1. (arg0: int) -> int" in str(err.value)
460+
assert "2. (arg0: {}) -> int".format(uni_name) in str(err.value)
461+
assert "3. (arg0: {}) -> int".format(uni_name) in str(err.value)
462+
assert "4. (arg0: int) -> int" in str(err.value)

tests/test_pytypes.cpp

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,16 +108,27 @@ TEST_SUBMODULE(pytypes, m) {
108108
});
109109

110110
m.def("return_capsule_with_name_and_destructor", []() {
111-
auto capsule = py::capsule((void *) 1234, "pointer type description", [](PyObject *ptr) {
111+
auto capsule = py::capsule((void *) 12345, "pointer type description", [](PyObject *ptr) {
112112
if (ptr) {
113113
auto name = PyCapsule_GetName(ptr);
114114
py::print("destructing capsule ({}, '{}')"_s.format(
115115
(size_t) PyCapsule_GetPointer(ptr, name), name
116116
));
117117
}
118118
});
119-
void *contents = capsule;
120-
py::print("created capsule ({}, '{}')"_s.format((size_t) contents, capsule.name()));
119+
120+
capsule.set_pointer((void *) 1234);
121+
122+
// Using get_pointer<T>()
123+
void* contents1 = static_cast<void*>(capsule);
124+
void* contents2 = capsule.get_pointer();
125+
void* contents3 = capsule.get_pointer<void>();
126+
127+
auto result1 = reinterpret_cast<size_t>(contents1);
128+
auto result2 = reinterpret_cast<size_t>(contents2);
129+
auto result3 = reinterpret_cast<size_t>(contents3);
130+
131+
py::print("created capsule ({}, '{}')"_s.format(result1 & result2 & result3, capsule.name()));
121132
return capsule;
122133
});
123134

0 commit comments

Comments
 (0)