Skip to content

Commit bd45ad0

Browse files
committed
Strip noexcept from cpp17 function type bindings
1 parent 1a86364 commit bd45ad0

5 files changed

Lines changed: 191 additions & 2 deletions

File tree

include/pybind11/detail/common.h

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -871,6 +871,14 @@ template <typename C, typename R, typename... A>
871871
struct remove_class<R (C::*)(A...) const noexcept> {
872872
using type = R(A...);
873873
};
874+
template <typename C, typename R, typename... A>
875+
struct remove_class<R (C::*)(A...) & noexcept> {
876+
using type = R(A...);
877+
};
878+
template <typename C, typename R, typename... A>
879+
struct remove_class<R (C::*)(A...) const & noexcept> {
880+
using type = R(A...);
881+
};
874882
#endif
875883
/// Helper template to strip away type modifiers
876884
template <typename T>
@@ -1056,14 +1064,30 @@ struct strip_function_object {
10561064
using type = typename remove_class<decltype(&F::operator())>::type;
10571065
};
10581066

1067+
// Strip noexcept from a free function type (C++17: noexcept is part of the type).
1068+
template <typename T>
1069+
struct remove_noexcept {
1070+
using type = T;
1071+
};
1072+
#ifdef __cpp_noexcept_function_type
1073+
template <typename R, typename... A>
1074+
struct remove_noexcept<R(A...) noexcept> {
1075+
using type = R(A...);
1076+
};
1077+
#endif
1078+
template <typename T>
1079+
using remove_noexcept_t = typename remove_noexcept<T>::type;
1080+
10591081
// Extracts the function signature from a function, function pointer or lambda.
1082+
// Strips noexcept from the result so that factory/pickle_factory partial specializations
1083+
// (which match plain Return(Args...)) work correctly with noexcept callables (issue #2234).
10601084
template <typename Function, typename F = remove_reference_t<Function>>
1061-
using function_signature_t = conditional_t<
1085+
using function_signature_t = remove_noexcept_t<conditional_t<
10621086
std::is_function<F>::value,
10631087
F,
10641088
typename conditional_t<std::is_pointer<F>::value || std::is_member_pointer<F>::value,
10651089
std::remove_pointer<F>,
1066-
strip_function_object<F>>::type>;
1090+
strip_function_object<F>>::type>>;
10671091

10681092
/// Returns true if the type looks like a lambda: that is, isn't a function, pointer or member
10691093
/// pointer. Note that this can catch all sorts of other things, too; this is intended to be used
@@ -1212,6 +1236,25 @@ struct overload_cast_impl {
12121236
-> decltype(pmf) {
12131237
return pmf;
12141238
}
1239+
1240+
#ifdef __cpp_noexcept_function_type
1241+
template <typename Return>
1242+
constexpr auto operator()(Return (*pf)(Args...) noexcept) const noexcept -> decltype(pf) {
1243+
return pf;
1244+
}
1245+
1246+
template <typename Return, typename Class>
1247+
constexpr auto operator()(Return (Class::*pmf)(Args...) noexcept,
1248+
std::false_type = {}) const noexcept -> decltype(pmf) {
1249+
return pmf;
1250+
}
1251+
1252+
template <typename Return, typename Class>
1253+
constexpr auto operator()(Return (Class::*pmf)(Args...) const noexcept,
1254+
std::true_type) const noexcept -> decltype(pmf) {
1255+
return pmf;
1256+
}
1257+
#endif
12151258
};
12161259
PYBIND11_NAMESPACE_END(detail)
12171260

include/pybind11/numpy.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2327,4 +2327,32 @@ Helper vectorize(Return (Class::*f)(Args...) const) {
23272327
return Helper(std::mem_fn(f));
23282328
}
23292329

2330+
#ifdef __cpp_noexcept_function_type
2331+
// Vectorize a class method (non-const, noexcept):
2332+
template <typename Return,
2333+
typename Class,
2334+
typename... Args,
2335+
typename Helper = detail::vectorize_helper<
2336+
decltype(std::mem_fn(std::declval<Return (Class::*)(Args...) noexcept>())),
2337+
Return,
2338+
Class *,
2339+
Args...>>
2340+
Helper vectorize(Return (Class::*f)(Args...) noexcept) {
2341+
return Helper(std::mem_fn(f));
2342+
}
2343+
2344+
// Vectorize a class method (const, noexcept):
2345+
template <typename Return,
2346+
typename Class,
2347+
typename... Args,
2348+
typename Helper = detail::vectorize_helper<
2349+
decltype(std::mem_fn(std::declval<Return (Class::*)(Args...) const noexcept>())),
2350+
Return,
2351+
const Class *,
2352+
Args...>>
2353+
Helper vectorize(Return (Class::*f)(Args...) const noexcept) {
2354+
return Helper(std::mem_fn(f));
2355+
}
2356+
#endif
2357+
23302358
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

include/pybind11/pybind11.h

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,48 @@ class cpp_function : public function {
370370
extra...);
371371
}
372372

373+
#ifdef __cpp_noexcept_function_type
374+
/// Construct a cpp_function from a class method (non-const, no ref-qualifier, noexcept)
375+
template <typename Return, typename Class, typename... Arg, typename... Extra>
376+
// NOLINTNEXTLINE(google-explicit-constructor)
377+
cpp_function(Return (Class::*f)(Arg...) noexcept, const Extra &...extra) {
378+
initialize(
379+
[f](Class *c, Arg... args) -> Return { return (c->*f)(std::forward<Arg>(args)...); },
380+
(Return (*)(Class *, Arg...)) nullptr,
381+
extra...);
382+
}
383+
384+
/// Construct a cpp_function from a class method (non-const, lvalue ref-qualifier, noexcept)
385+
template <typename Return, typename Class, typename... Arg, typename... Extra>
386+
// NOLINTNEXTLINE(google-explicit-constructor)
387+
cpp_function(Return (Class::*f)(Arg...) & noexcept, const Extra &...extra) {
388+
initialize(
389+
[f](Class *c, Arg... args) -> Return { return (c->*f)(std::forward<Arg>(args)...); },
390+
(Return (*)(Class *, Arg...)) nullptr,
391+
extra...);
392+
}
393+
394+
/// Construct a cpp_function from a class method (const, no ref-qualifier, noexcept)
395+
template <typename Return, typename Class, typename... Arg, typename... Extra>
396+
// NOLINTNEXTLINE(google-explicit-constructor)
397+
cpp_function(Return (Class::*f)(Arg...) const noexcept, const Extra &...extra) {
398+
initialize([f](const Class *c,
399+
Arg... args) -> Return { return (c->*f)(std::forward<Arg>(args)...); },
400+
(Return (*)(const Class *, Arg...)) nullptr,
401+
extra...);
402+
}
403+
404+
/// Construct a cpp_function from a class method (const, lvalue ref-qualifier, noexcept)
405+
template <typename Return, typename Class, typename... Arg, typename... Extra>
406+
// NOLINTNEXTLINE(google-explicit-constructor)
407+
cpp_function(Return (Class::*f)(Arg...) const & noexcept, const Extra &...extra) {
408+
initialize([f](const Class *c,
409+
Arg... args) -> Return { return (c->*f)(std::forward<Arg>(args)...); },
410+
(Return (*)(const Class *, Arg...)) nullptr,
411+
extra...);
412+
}
413+
#endif
414+
373415
/// Return the function name
374416
object name() const { return attr("__name__"); }
375417

@@ -1905,6 +1947,26 @@ auto method_adaptor(Return (Class::*pmf)(Args...) const) -> Return (Derived::*)(
19051947
return pmf;
19061948
}
19071949

1950+
#ifdef __cpp_noexcept_function_type
1951+
template <typename Derived, typename Return, typename Class, typename... Args>
1952+
auto method_adaptor(Return (Class::*pmf)(Args...) noexcept)
1953+
-> Return (Derived::*)(Args...) noexcept {
1954+
static_assert(
1955+
detail::is_accessible_base_of<Class, Derived>::value,
1956+
"Cannot bind an inaccessible base class method; use a lambda definition instead");
1957+
return pmf;
1958+
}
1959+
1960+
template <typename Derived, typename Return, typename Class, typename... Args>
1961+
auto method_adaptor(Return (Class::*pmf)(Args...) const noexcept)
1962+
-> Return (Derived::*)(Args...) const noexcept {
1963+
static_assert(
1964+
detail::is_accessible_base_of<Class, Derived>::value,
1965+
"Cannot bind an inaccessible base class method; use a lambda definition instead");
1966+
return pmf;
1967+
}
1968+
#endif
1969+
19081970
PYBIND11_NAMESPACE_BEGIN(detail)
19091971

19101972
// Helper for the property_cpp_function static member functions below.
@@ -2361,6 +2423,18 @@ class class_ : public detail::generic_type {
23612423
return def_buffer([func](const type &obj) { return (obj.*func)(); });
23622424
}
23632425

2426+
#ifdef __cpp_noexcept_function_type
2427+
template <typename Return, typename Class, typename... Args>
2428+
class_ &def_buffer(Return (Class::*func)(Args...) noexcept) {
2429+
return def_buffer([func](type &obj) { return (obj.*func)(); });
2430+
}
2431+
2432+
template <typename Return, typename Class, typename... Args>
2433+
class_ &def_buffer(Return (Class::*func)(Args...) const noexcept) {
2434+
return def_buffer([func](const type &obj) { return (obj.*func)(); });
2435+
}
2436+
#endif
2437+
23642438
template <typename C, typename D, typename... Extra>
23652439
class_ &def_readwrite(const char *name, D C::*pm, const Extra &...extra) {
23662440
static_assert(std::is_same<C, type>::value || std::is_base_of<C, type>::value,

tests/test_methods_and_attributes.cpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,22 @@ class RegisteredDerived : public UnregisteredBase {
161161
double sum() const { return rw_value + ro_value; }
162162
};
163163

164+
// Issue #2234: noexcept methods in an unregistered base should be bindable on the derived class
165+
// In C++17, noexcept is part of the function type, so &Derived::method resolves to
166+
// a Base member function pointer with noexcept, requiring explicit template specializations.
167+
class NoexceptUnregisteredBase {
168+
public:
169+
int value() const noexcept { return m_value; }
170+
void set_value(int v) noexcept { m_value = v; }
171+
172+
private:
173+
int m_value = 99;
174+
};
175+
class NoexceptDerived : public NoexceptUnregisteredBase {
176+
public:
177+
using NoexceptUnregisteredBase::NoexceptUnregisteredBase;
178+
};
179+
164180
// Test explicit lvalue ref-qualification
165181
struct RefQualified {
166182
int value = 0;
@@ -474,6 +490,21 @@ TEST_SUBMODULE(methods_and_attributes, m) {
474490
= decltype(py::method_adaptor<RegisteredDerived>(&RegisteredDerived::do_nothing));
475491
static_assert(std::is_same<Adapted, void (RegisteredDerived::*)() const>::value, "");
476492

493+
// test_noexcept_base (issue #2234)
494+
// In C++17, noexcept is part of the function type. Binding a noexcept method from an
495+
// unregistered base class must resolve `self` to the derived type, not the base type.
496+
py::class_<NoexceptDerived>(m, "NoexceptDerived")
497+
.def(py::init<>())
498+
.def("value", &NoexceptDerived::value)
499+
.def("set_value", &NoexceptDerived::set_value);
500+
501+
#ifdef __cpp_noexcept_function_type
502+
// method_adaptor must also handle noexcept member function pointers (issue #2234)
503+
using AdaptedNoexcept = decltype(py::method_adaptor<NoexceptDerived>(&NoexceptDerived::value));
504+
static_assert(std::is_same<AdaptedNoexcept, int (NoexceptDerived::*)() const noexcept>::value,
505+
"");
506+
#endif
507+
477508
// test_methods_and_attributes
478509
py::class_<RefQualified>(m, "RefQualified")
479510
.def(py::init<>())

tests/test_methods_and_attributes.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,19 @@ def test_unregistered_base_implementations():
517517
assert a.ro_value_prop == 1.75
518518

519519

520+
def test_noexcept_base():
521+
"""Test issue #2234: binding noexcept methods inherited from an unregistered base class.
522+
523+
In C++17 noexcept is part of the function type, so &Derived::noexcept_method resolves
524+
to a Base member-function pointer with noexcept specifier. pybind11 must use the Derived
525+
type as `self`, not the Base type, otherwise the call raises TypeError at runtime.
526+
"""
527+
obj = m.NoexceptDerived()
528+
assert obj.value() == 99
529+
obj.set_value(7)
530+
assert obj.value() == 7
531+
532+
520533
def test_ref_qualified():
521534
"""Tests that explicit lvalue ref-qualified methods can be called just like their
522535
non ref-qualified counterparts."""

0 commit comments

Comments
 (0)