@@ -161,13 +161,19 @@ 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
164+ // Issue #2234: noexcept methods in an unregistered base should be bindable on the derived class.
165165// In C++17, noexcept is part of the function type, so &Derived::method resolves to
166166// a Base member function pointer with noexcept, requiring explicit template specializations.
167167class NoexceptUnregisteredBase {
168168public:
169+ // Exercises cpp_function(Return (Class::*)(Args...) const noexcept, ...)
169170 int value () const noexcept { return m_value; }
171+ // Exercises cpp_function(Return (Class::*)(Args...) noexcept, ...)
170172 void set_value (int v) noexcept { m_value = v; }
173+ // Exercises cpp_function(Return (Class::*)(Args...) & noexcept, ...)
174+ void increment () & noexcept { ++m_value; }
175+ // Exercises cpp_function(Return (Class::*)(Args...) const & noexcept, ...)
176+ int capped_value () const & noexcept { return m_value < 100 ? m_value : 100 ; }
171177
172178private:
173179 int m_value = 99 ;
@@ -177,6 +183,17 @@ class NoexceptDerived : public NoexceptUnregisteredBase {
177183 using NoexceptUnregisteredBase::NoexceptUnregisteredBase;
178184};
179185
186+ // Exercises overload_cast with noexcept member function pointers (issue #2234).
187+ // In C++17, overload_cast must have noexcept variants to resolve noexcept overloads.
188+ struct NoexceptOverloaded {
189+ py::str method (int ) noexcept { return " (int)" ; }
190+ py::str method (int ) const noexcept { return " (int) const" ; }
191+ py::str method (float ) noexcept { return " (float)" ; }
192+ };
193+ // Exercises overload_cast with noexcept free function pointers.
194+ int noexcept_free_func (int x) noexcept { return x + 1 ; }
195+ int noexcept_free_func (float x) noexcept { return static_cast <int >(x) + 2 ; }
196+
180197// Test explicit lvalue ref-qualification
181198struct RefQualified {
182199 int value = 0 ;
@@ -495,16 +512,56 @@ TEST_SUBMODULE(methods_and_attributes, m) {
495512 // unregistered base class must resolve `self` to the derived type, not the base type.
496513 py::class_<NoexceptDerived>(m, " NoexceptDerived" )
497514 .def (py::init<>())
515+ // cpp_function(Return (Class::*)(Args...) const noexcept, ...)
498516 .def (" value" , &NoexceptDerived::value)
499- .def (" set_value" , &NoexceptDerived::set_value);
517+ // cpp_function(Return (Class::*)(Args...) noexcept, ...)
518+ .def (" set_value" , &NoexceptDerived::set_value)
519+ // cpp_function(Return (Class::*)(Args...) & noexcept, ...)
520+ .def (" increment" , &NoexceptDerived::increment)
521+ // cpp_function(Return (Class::*)(Args...) const & noexcept, ...)
522+ .def (" capped_value" , &NoexceptDerived::capped_value);
500523
501524#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,
525+ // method_adaptor must also handle noexcept member function pointers (issue #2234).
526+ // Verify the noexcept specifier is preserved in the resulting Derived pointer type.
527+ using AdaptedConstNoexcept
528+ = decltype (py::method_adaptor<NoexceptDerived>(&NoexceptDerived::value));
529+ static_assert (
530+ std::is_same<AdaptedConstNoexcept, int (NoexceptDerived::*)() const noexcept >::value, " " );
531+ using AdaptedNoexcept
532+ = decltype (py::method_adaptor<NoexceptDerived>(&NoexceptDerived::set_value));
533+ static_assert (std::is_same<AdaptedNoexcept, void (NoexceptDerived::*)(int ) noexcept >::value,
505534 " " );
506535#endif
507536
537+ // test_noexcept_overload_cast (issue #2234)
538+ // overload_cast must have noexcept operator() overloads so it can resolve noexcept methods.
539+ #ifdef PYBIND11_OVERLOAD_CAST
540+ py::class_<NoexceptOverloaded>(m, " NoexceptOverloaded" )
541+ .def (py::init<>())
542+ // overload_cast_impl::operator()(Return (Class::*)(Args...) noexcept, false_type)
543+ .def (" method" , py::overload_cast<int >(&NoexceptOverloaded::method))
544+ // overload_cast_impl::operator()(Return (Class::*)(Args...) const noexcept, true_type)
545+ .def (" method_const" , py::overload_cast<int >(&NoexceptOverloaded::method, py::const_))
546+ // overload_cast_impl::operator()(Return (Class::*)(Args...) noexcept, false_type) float
547+ .def (" method_float" , py::overload_cast<float >(&NoexceptOverloaded::method));
548+ // overload_cast_impl::operator()(Return (*)(Args...) noexcept)
549+ m.def (" noexcept_free_func" , py::overload_cast<int >(noexcept_free_func));
550+ m.def (" noexcept_free_func_float" , py::overload_cast<float >(noexcept_free_func));
551+ #else
552+ // Fallback using explicit static_cast for C++11/14
553+ py::class_<NoexceptOverloaded>(m, " NoexceptOverloaded" )
554+ .def (py::init<>())
555+ .def (" method" ,
556+ static_cast <py::str (NoexceptOverloaded::*)(int )>(&NoexceptOverloaded::method))
557+ .def (" method_const" ,
558+ static_cast <py::str (NoexceptOverloaded::*)(int ) const >(&NoexceptOverloaded::method))
559+ .def (" method_float" ,
560+ static_cast <py::str (NoexceptOverloaded::*)(float )>(&NoexceptOverloaded::method));
561+ m.def (" noexcept_free_func" , static_cast <int (*)(int )>(noexcept_free_func));
562+ m.def (" noexcept_free_func_float" , static_cast <int (*)(float )>(noexcept_free_func));
563+ #endif
564+
508565 // test_methods_and_attributes
509566 py::class_<RefQualified>(m, " RefQualified" )
510567 .def (py::init<>())
0 commit comments