Support binding noexcept function/methods in C++17

When compiling in C++17 mode the noexcept specifier is part of the
function type.  This causes a failure in pybind11 because, by omitting
a noexcept specifier when deducing function return and argument types,
we are implicitly making `noexcept(false)` part of the type.

This means that functions with `noexcept` fail to match the function
templates in cpp_function (and other places), and we get compilation
failure (we end up trying to fit it into the lambda function version,
which fails since a function pointer has no `operator()`).

We can, however, deduce the true/false `B` in noexcept(B), so we don't
need to add a whole other set of overloads, but need to deduce the extra
argument when under C++17.  That will *not* work under pre-C++17,
however.

This commit adds two macros to fix the problem: under C++17 (with the
appropriate feature macro set) they provide an extra `bool NoExceptions`
template argument and provide the `noexcept(NoExceptions)` deduced
specifier.  Under pre-C++17 they expand to nothing.

This is needed to compile pybind11 with gcc7 under -std=c++17.
This commit is contained in:
Jason Rhinelander 2016-12-13 20:06:41 -05:00 committed by Wenzel Jakob
parent 79de508ef4
commit 6e036e78a7
6 changed files with 100 additions and 28 deletions

View File

@ -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 <typename T> void operator()(T*) { } };
NAMESPACE_BEGIN(detail)
template <typename... Args>
struct overload_cast_impl {
template <typename Return>
constexpr auto operator()(Return (*pf)(Args...)) const noexcept
template <typename Return /*,*/ PYBIND11_NOEXCEPT_TPL_ARG>
constexpr auto operator()(Return (*pf)(Args...) PYBIND11_NOEXCEPT_SPECIFIER) const noexcept
-> decltype(pf) { return pf; }
template <typename Return, typename Class>
constexpr auto operator()(Return (Class::*pmf)(Args...), std::false_type = {}) const noexcept
template <typename Return, typename Class /*,*/ PYBIND11_NOEXCEPT_TPL_ARG>
constexpr auto operator()(Return (Class::*pmf)(Args...) PYBIND11_NOEXCEPT_SPECIFIER, std::false_type = {}) const noexcept
-> decltype(pmf) { return pmf; }
template <typename Return, typename Class>
constexpr auto operator()(Return (Class::*pmf)(Args...) const, std::true_type) const noexcept
template <typename Return, typename Class /*,*/ PYBIND11_NOEXCEPT_TPL_ARG>
constexpr auto operator()(Return (Class::*pmf)(Args...) const PYBIND11_NOEXCEPT_SPECIFIER, std::true_type) const noexcept
-> decltype(pmf) { return pmf; }
};
NAMESPACE_END(detail)

View File

@ -15,9 +15,12 @@
NAMESPACE_BEGIN(pybind11)
NAMESPACE_BEGIN(detail)
template <typename Return, typename... Args> struct type_caster<std::function<Return(Args...)>> {
typedef std::function<Return(Args...)> type;
typedef typename std::conditional<std::is_same<Return, void>::value, void_type, Return>::type retval_type;
template <typename Return, typename... Args /*,*/ PYBIND11_NOEXCEPT_TPL_ARG>
struct type_caster<std::function<Return(Args...) PYBIND11_NOEXCEPT_SPECIFIER>> {
using type = std::function<Return(Args...) PYBIND11_NOEXCEPT_SPECIFIER>;
using retval_type = conditional_t<std::is_same<Return, void>::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<capsule>(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<Return (*)(Args...)>();
auto result = f_.template target<function_type>();
if (result)
return cpp_function(*result, policy).release();
else

View File

@ -1145,13 +1145,15 @@ template <typename T, int Flags> struct handle_type_name<array_t<T, Flags>> {
NAMESPACE_END(detail)
template <typename Func, typename Return, typename... Args>
detail::vectorize_helper<Func, Return, Args...> vectorize(const Func &f, Return (*) (Args ...)) {
template <typename Func, typename Return, typename... Args /*,*/ PYBIND11_NOEXCEPT_TPL_ARG>
detail::vectorize_helper<Func, Return, Args...>
vectorize(const Func &f, Return (*) (Args ...) PYBIND11_NOEXCEPT_SPECIFIER) {
return detail::vectorize_helper<Func, Return, Args...>(f);
}
template <typename Return, typename... Args>
detail::vectorize_helper<Return (*) (Args ...), Return, Args...> vectorize(Return (*f) (Args ...)) {
template <typename Return, typename... Args /*,*/ PYBIND11_NOEXCEPT_TPL_ARG>
detail::vectorize_helper<Return (*) (Args ...) PYBIND11_NOEXCEPT_SPECIFIER, Return, Args...>
vectorize(Return (*f) (Args ...) PYBIND11_NOEXCEPT_SPECIFIER) {
return vectorize<Return (*) (Args ...), Return, Args...>(f, f);
}

View File

@ -44,8 +44,8 @@ public:
cpp_function() { }
/// Construct a cpp_function from a vanilla function pointer
template <typename Return, typename... Args, typename... Extra>
cpp_function(Return (*f)(Args...), const Extra&... extra) {
template <typename Return, typename... Args, typename... Extra /*,*/ PYBIND11_NOEXCEPT_TPL_ARG>
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 <typename Return, typename Class, typename... Arg, typename... Extra>
cpp_function(Return (Class::*f)(Arg...), const Extra&... extra) {
template <typename Return, typename Class, typename... Arg, typename... Extra /*,*/ PYBIND11_NOEXCEPT_TPL_ARG>
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 <typename Return, typename Class, typename... Arg, typename... Extra>
cpp_function(Return (Class::*f)(Arg...) const, const Extra&... extra) {
template <typename Return, typename Class, typename... Arg, typename... Extra /*,*/ PYBIND11_NOEXCEPT_TPL_ARG>
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 <typename Func, typename Return, typename... Args, typename... Extra>
void initialize(Func &&f, Return (*)(Args...), const Extra&... extra) {
template <typename Func, typename Return, typename... Args, typename... Extra /*,*/ PYBIND11_NOEXCEPT_TPL_ARG>
void initialize(Func &&f, Return (*)(Args...) PYBIND11_NOEXCEPT_SPECIFIER, const Extra&... extra) {
static_assert(detail::expected_num_args<Extra...>(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<Func, FunctionType>::value &&
sizeof(capture) == sizeof(void *);

View File

@ -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_<C>(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);
});

View File

@ -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