diff --git a/include/pybind11/common.h b/include/pybind11/common.h index 712c1a5d..dcb58107 100644 --- a/include/pybind11/common.h +++ b/include/pybind11/common.h @@ -179,6 +179,17 @@ extern "C" { } \ PyObject *pybind11_init() +// Function return value and argument type deduction support. When compiling under C++17 these +// differ as C++17 makes the noexcept specifier part of the function type, while it is not part of +// the type under earlier standards. +#ifdef __cpp_noexcept_function_type +# define PYBIND11_NOEXCEPT_TPL_ARG , bool NoExceptions +# define PYBIND11_NOEXCEPT_SPECIFIER noexcept(NoExceptions) +#else +# define PYBIND11_NOEXCEPT_TPL_ARG +# define PYBIND11_NOEXCEPT_SPECIFIER +#endif + NAMESPACE_BEGIN(pybind11) using ssize_t = Py_ssize_t; @@ -564,16 +575,16 @@ struct nodelete { template void operator()(T*) { } }; NAMESPACE_BEGIN(detail) template struct overload_cast_impl { - template - constexpr auto operator()(Return (*pf)(Args...)) const noexcept + template + constexpr auto operator()(Return (*pf)(Args...) PYBIND11_NOEXCEPT_SPECIFIER) const noexcept -> decltype(pf) { return pf; } - template - constexpr auto operator()(Return (Class::*pmf)(Args...), std::false_type = {}) const noexcept + template + constexpr auto operator()(Return (Class::*pmf)(Args...) PYBIND11_NOEXCEPT_SPECIFIER, std::false_type = {}) const noexcept -> decltype(pmf) { return pmf; } - template - constexpr auto operator()(Return (Class::*pmf)(Args...) const, std::true_type) const noexcept + template + constexpr auto operator()(Return (Class::*pmf)(Args...) const PYBIND11_NOEXCEPT_SPECIFIER, std::true_type) const noexcept -> decltype(pmf) { return pmf; } }; NAMESPACE_END(detail) diff --git a/include/pybind11/functional.h b/include/pybind11/functional.h index f1b0ebbb..7f1ffc1d 100644 --- a/include/pybind11/functional.h +++ b/include/pybind11/functional.h @@ -15,9 +15,12 @@ NAMESPACE_BEGIN(pybind11) NAMESPACE_BEGIN(detail) -template struct type_caster> { - typedef std::function type; - typedef typename std::conditional::value, void_type, Return>::type retval_type; +template +struct type_caster> { + using type = std::function; + using retval_type = conditional_t::value, void_type, Return>; + using function_type = Return (*) (Args...) PYBIND11_NOEXCEPT_SPECIFIER; + public: bool load(handle src_, bool) { if (src_.is_none()) @@ -38,10 +41,9 @@ public: if (PyCFunction_Check(src_.ptr())) { auto c = reinterpret_borrow(PyCFunction_GetSelf(src_.ptr())); auto rec = (function_record *) c; - using FunctionType = Return (*) (Args...); - if (rec && rec->is_stateless && rec->data[1] == &typeid(FunctionType)) { - struct capture { FunctionType f; }; + if (rec && rec->is_stateless && rec->data[1] == &typeid(function_type)) { + struct capture { function_type f; }; value = ((capture *) &rec->data)->f; return true; } @@ -62,7 +64,7 @@ public: if (!f_) return none().inc_ref(); - auto result = f_.template target(); + auto result = f_.template target(); if (result) return cpp_function(*result, policy).release(); else diff --git a/include/pybind11/numpy.h b/include/pybind11/numpy.h index e6f4efdf..7d46f107 100644 --- a/include/pybind11/numpy.h +++ b/include/pybind11/numpy.h @@ -1145,13 +1145,15 @@ template struct handle_type_name> { NAMESPACE_END(detail) -template -detail::vectorize_helper vectorize(const Func &f, Return (*) (Args ...)) { +template +detail::vectorize_helper +vectorize(const Func &f, Return (*) (Args ...) PYBIND11_NOEXCEPT_SPECIFIER) { return detail::vectorize_helper(f); } -template -detail::vectorize_helper vectorize(Return (*f) (Args ...)) { +template +detail::vectorize_helper +vectorize(Return (*f) (Args ...) PYBIND11_NOEXCEPT_SPECIFIER) { return vectorize(f, f); } diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 6c884a86..1492cd29 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -44,8 +44,8 @@ public: cpp_function() { } /// Construct a cpp_function from a vanilla function pointer - template - cpp_function(Return (*f)(Args...), const Extra&... extra) { + template + cpp_function(Return (*f)(Args...) PYBIND11_NOEXCEPT_SPECIFIER, const Extra&... extra) { initialize(f, f, extra...); } @@ -57,17 +57,17 @@ public: } /// Construct a cpp_function from a class method (non-const) - template - cpp_function(Return (Class::*f)(Arg...), const Extra&... extra) { + template + cpp_function(Return (Class::*f)(Arg...) PYBIND11_NOEXCEPT_SPECIFIER, const Extra&... extra) { initialize([f](Class *c, Arg... args) -> Return { return (c->*f)(args...); }, - (Return (*) (Class *, Arg...)) nullptr, extra...); + (Return (*) (Class *, Arg...) PYBIND11_NOEXCEPT_SPECIFIER) nullptr, extra...); } /// Construct a cpp_function from a class method (const) - template - cpp_function(Return (Class::*f)(Arg...) const, const Extra&... extra) { + template + cpp_function(Return (Class::*f)(Arg...) const PYBIND11_NOEXCEPT_SPECIFIER, const Extra&... extra) { initialize([f](const Class *c, Arg... args) -> Return { return (c->*f)(args...); }, - (Return (*)(const Class *, Arg ...)) nullptr, extra...); + (Return (*)(const Class *, Arg ...) PYBIND11_NOEXCEPT_SPECIFIER) nullptr, extra...); } /// Return the function name @@ -80,8 +80,8 @@ protected: } /// Special internal constructor for functors, lambda functions, etc. - template - void initialize(Func &&f, Return (*)(Args...), const Extra&... extra) { + template + void initialize(Func &&f, Return (*)(Args...) PYBIND11_NOEXCEPT_SPECIFIER, const Extra&... extra) { static_assert(detail::expected_num_args(sizeof...(Args)), "The number of named arguments does not match the function signature"); @@ -160,7 +160,7 @@ protected: if (cast_in::has_kwargs) rec->has_kwargs = true; /* Stash some additional information used by an important optimization in 'functional.h' */ - using FunctionType = Return (*)(Args...); + using FunctionType = Return (*)(Args...) PYBIND11_NOEXCEPT_SPECIFIER; constexpr bool is_function_ptr = std::is_convertible::value && sizeof(capture) == sizeof(void *); diff --git a/tests/test_constants_and_functions.cpp b/tests/test_constants_and_functions.cpp index c8c0392c..653bdf6b 100644 --- a/tests/test_constants_and_functions.cpp +++ b/tests/test_constants_and_functions.cpp @@ -41,6 +41,26 @@ std::string print_bytes(py::bytes bytes) { return ret; } +// Test that we properly handle C++17 exception specifiers (which are part of the function signature +// in C++17). These should all still work before C++17, but don't affect the function signature. +namespace test_exc_sp { +int f1(int x) noexcept { return x+1; } +int f2(int x) noexcept(true) { return x+2; } +int f3(int x) noexcept(false) { return x+3; } +int f4(int x) throw() { return x+4; } // Deprecated equivalent to noexcept(true) +struct C { + int m1(int x) noexcept { return x-1; } + int m2(int x) const noexcept { return x-2; } + int m3(int x) noexcept(true) { return x-3; } + int m4(int x) const noexcept(true) { return x-4; } + int m5(int x) noexcept(false) { return x-5; } + int m6(int x) const noexcept(false) { return x-6; } + int m7(int x) throw() { return x-7; } + int m8(int x) const throw() { return x-8; } +}; +} + + test_initializer constants_and_functions([](py::module &m) { m.attr("some_constant") = py::int_(14); @@ -63,4 +83,22 @@ test_initializer constants_and_functions([](py::module &m) { m.def("return_bytes", &return_bytes); m.def("print_bytes", &print_bytes); + + using namespace test_exc_sp; + py::module m2 = m.def_submodule("exc_sp"); + py::class_(m2, "C") + .def(py::init<>()) + .def("m1", &C::m1) + .def("m2", &C::m2) + .def("m3", &C::m3) + .def("m4", &C::m4) + .def("m5", &C::m5) + .def("m6", &C::m6) + .def("m7", &C::m7) + .def("m8", &C::m8) + ; + m2.def("f1", f1); + m2.def("f2", f2); + m2.def("f3", f3); + m2.def("f4", f4); }); diff --git a/tests/test_constants_and_functions.py b/tests/test_constants_and_functions.py index d13d3af1..2a570d2e 100644 --- a/tests/test_constants_and_functions.py +++ b/tests/test_constants_and_functions.py @@ -22,3 +22,22 @@ def test_bytes(): from pybind11_tests import return_bytes, print_bytes assert print_bytes(return_bytes()) == "bytes[1 0 2 0]" + + +def test_exception_specifiers(): + from pybind11_tests.exc_sp import C, f1, f2, f3, f4 + + c = C() + assert c.m1(2) == 1 + assert c.m2(3) == 1 + assert c.m3(5) == 2 + assert c.m4(7) == 3 + assert c.m5(10) == 5 + assert c.m6(14) == 8 + assert c.m7(20) == 13 + assert c.m8(29) == 21 + + assert f1(33) == 34 + assert f2(53) == 55 + assert f3(86) == 89 + assert f4(140) == 144