Skip to content

Commit 1c2f982

Browse files
committed
Add a priority overload with py::prepend
1 parent 6c62d27 commit 1c2f982

File tree

7 files changed

+69
-7
lines changed

7 files changed

+69
-7
lines changed

docs/changelog.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ v2.3.0 (Not yet released)
1414
precomputed at compile time (this was previously only available in C++14 mode
1515
for non-MSVC compilers).
1616
`#934 <https://github.com/pybind/pybind11/pull/934>`_.
17+
* Added ``py::prepend()``, allowing a function to be placed at the beginning of the
18+
overload chain `#1131 <https://github.com/pybind/pybind11/pull/1131>`_.
1719

1820
* Added support for write only properties.
1921
`#1144 <https://github.com/pybind/pybind11/pull/1144>`_.

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 & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ struct module_local { const bool value; constexpr module_local(bool v = true) :
7070
/// Annotation to mark enums as an arithmetic type
7171
struct arithmetic { };
7272

73+
/// Mark a function for addition at the beginning of the existing overload chain instead of the end
74+
struct prepend { };
75+
7376
/** \rst
7477
A call policy which places one or more guard variables (``Ts...``) around the function call.
7578
@@ -134,7 +137,8 @@ struct argument_record {
134137
struct function_record {
135138
function_record()
136139
: is_constructor(false), is_new_style_constructor(false), is_stateless(false),
137-
is_operator(false), has_args(false), has_kwargs(false), is_method(false) { }
140+
is_operator(false), has_args(false), has_kwargs(false), is_method(false),
141+
is_prepended(false) { }
138142

139143
/// Function name
140144
char *name = nullptr; /* why no C++ strings? They generate heavier code.. */
@@ -181,6 +185,9 @@ struct function_record {
181185
/// True if this is a method
182186
bool is_method : 1;
183187

188+
/// True if this function is to be inserted at the beginning of the overload resolution chain
189+
bool is_prepended : 1;
190+
184191
/// Number of arguments (including py::args and/or py::kwargs, if present)
185192
std::uint16_t nargs;
186193

@@ -427,6 +434,11 @@ struct process_attribute<module_local> : process_attribute_default<module_local>
427434
static void init(const module_local &l, type_record *r) { r->module_local = l.value; }
428435
};
429436

437+
/// Process an 'prepend' attribute, putting this at the top of the overload chain
438+
template <> struct process_attribute<prepend> : process_attribute_default<prepend> {
439+
static void init(const prepend &, function_record *r) { r->is_prepended = true; }
440+
};
441+
430442
/// Process an 'arithmetic' attribute for enums (does nothing here)
431443
template <>
432444
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
@@ -326,10 +326,9 @@ class cpp_function : public function {
326326
if (!m_ptr)
327327
pybind11_fail("cpp_function::cpp_function(): Could not allocate function object");
328328
} else {
329-
/* Append at the end of the overload chain */
329+
/* Append at the beginning or end of the overload chain */
330330
m_ptr = rec->sibling.ptr();
331331
inc_ref();
332-
chain_start = chain;
333332
if (chain->is_method != rec->is_method)
334333
pybind11_fail("overloading a method with both static and instance methods is not supported; "
335334
#if defined(NDEBUG)
@@ -339,9 +338,22 @@ class cpp_function : public function {
339338
std::string(pybind11::str(rec->scope.attr("__name__"))) + "." + std::string(rec->name) + signature
340339
#endif
341340
);
342-
while (chain->next)
343-
chain = chain->next;
344-
chain->next = rec;
341+
342+
if (rec->is_prepended) {
343+
// Beginning of chain; we need to replace the capsule's current head-of-the-chain
344+
// pointer with this one, then make this one point to the previous head of the
345+
// chain.
346+
chain_start = rec;
347+
rec->next = chain;
348+
auto rec_capsule = reinterpret_borrow<capsule>(((PyCFunctionObject *) m_ptr)->m_self);
349+
rec_capsule.set_pointer(rec);
350+
} else {
351+
// Or end of chain (normal behavior)
352+
chain_start = chain;
353+
while (chain->next)
354+
chain = chain->next;
355+
chain->next = rec;
356+
}
345357
}
346358

347359
std::string signatures;

include/pybind11/pytypes.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1129,6 +1129,12 @@ class capsule : public object {
11291129
return result;
11301130
}
11311131

1132+
// Replaces a capsule's pointer *without* calling the destructor on the existing one.
1133+
void set_pointer(const void *value) {
1134+
if (PyCapsule_SetPointer(m_ptr, const_cast<void *>(value)) != 0)
1135+
pybind11_fail("Could not set capsule pointer");
1136+
}
1137+
11321138
const char *name() const { return PyCapsule_GetName(m_ptr); }
11331139
};
11341140

tests/test_methods_and_attributes.cpp

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

338+
// test_overload_ordering
339+
m.def("overload_order", [](std::string) { return 1; });
340+
m.def("overload_order", [](std::string) { return 2; });
341+
m.def("overload_order", [](int) { return 3; });
342+
m.def("overload_order", [](int) { return 4; }, py::prepend{});
343+
338344
#if !defined(PYPY_VERSION)
339345
// test_dynamic_attributes
340346
class DynamicClass {

tests/test_methods_and_attributes.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,3 +510,25 @@ def test_custom_caster_destruction():
510510

511511
# Make sure we still only have the original object (from ..._no_destroy()) alive:
512512
assert cstats.alive() == 1
513+
514+
515+
def test_overload_ordering():
516+
'Check to see if the normal overload order (first defined) and prepend overload order works'
517+
assert m.overload_order("string") == 1
518+
assert m.overload_order(0) == 4
519+
520+
# Different for Python 2 vs. 3
521+
uni_name = type(u"").__name__
522+
523+
assert "1. overload_order(arg0: int) -> int" in m.overload_order.__doc__
524+
assert "2. overload_order(arg0: {}) -> int".format(uni_name) in m.overload_order.__doc__
525+
assert "3. overload_order(arg0: {}) -> int".format(uni_name) in m.overload_order.__doc__
526+
assert "4. overload_order(arg0: int) -> int" in m.overload_order.__doc__
527+
528+
with pytest.raises(TypeError) as err:
529+
m.overload_order(1.1)
530+
531+
assert "1. (arg0: int) -> int" in str(err.value)
532+
assert "2. (arg0: {}) -> int".format(uni_name) in str(err.value)
533+
assert "3. (arg0: {}) -> int".format(uni_name) in str(err.value)
534+
assert "4. (arg0: int) -> int" in str(err.value)

0 commit comments

Comments
 (0)