Skip to content

Commit 4b1a38a

Browse files
committed
feat: add a priority overload with py::prepend
1 parent 56784c4 commit 4b1a38a

File tree

7 files changed

+70
-8
lines changed

7 files changed

+70
-8
lines changed

docs/changelog.rst

Lines changed: 3 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>`_

docs/classes.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,9 @@ Attempting to bind ``Pet::set`` will cause an error since the compiler does not
367367
know which method the user intended to select. We can disambiguate by casting
368368
them to function pointers. Binding multiple functions to the same Python name
369369
automatically creates a chain of function overloads that will be tried in
370-
sequence.
370+
the order they were defined. If the ``py::prepend()`` tag is added to the
371+
definition, the function will be placed at the begining of the overload sequence
372+
instead.
371373

372374
.. code-block:: cpp
373375

include/pybind11/attr.h

Lines changed: 13 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), is_prepended(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 is_prepended : 1;
197+
192198
/// Number of arguments (including py::args and/or py::kwargs, if present)
193199
std::uint16_t nargs;
194200

@@ -477,6 +483,11 @@ 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 an 'prepend' attribute, putting this at the top of the overload chain
487+
template <> struct process_attribute<prepend> : process_attribute_default<prepend> {
488+
static void init(const prepend &, function_record *r) { r->is_prepended = true; }
489+
};
490+
480491
/// Process an 'arithmetic' attribute for enums (does nothing here)
481492
template <>
482493
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->is_prepended) {
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: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1235,6 +1235,12 @@ class capsule : public object {
12351235
return result;
12361236
}
12371237

1238+
// Replaces a capsule's pointer *without* calling the destructor on the existing one.
1239+
void set_pointer(const void *value) {
1240+
if (PyCapsule_SetPointer(m_ptr, const_cast<void *>(value)) != 0)
1241+
pybind11_fail("Could not set capsule pointer");
1242+
}
1243+
12381244
const char *name() const { return PyCapsule_GetName(m_ptr); }
12391245
};
12401246

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)

0 commit comments

Comments
 (0)