Skip to content

Commit 2c0a1e0

Browse files
committed
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.
1 parent 8c4bd81 commit 2c0a1e0

File tree

6 files changed

+99
-28
lines changed

6 files changed

+99
-28
lines changed

include/pybind11/common.h

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,17 @@ extern "C" {
179179
} \
180180
PyObject *pybind11_init()
181181

182+
// Function return value and argument type deduction support. When compiling under C++17 these
183+
// differ as C++17 makes the noexcept specifier part of the function type, while it is not part of
184+
// the type under earlier standards.
185+
#ifdef __cpp_noexcept_function_type
186+
# define PYBIND11_NOEXCEPT_TPL_ARG , bool NoExceptions
187+
# define PYBIND11_NOEXCEPT_SPECIFIER noexcept(NoExceptions)
188+
#else
189+
# define PYBIND11_NOEXCEPT_TPL_ARG
190+
# define PYBIND11_NOEXCEPT_SPECIFIER
191+
#endif
192+
182193
NAMESPACE_BEGIN(pybind11)
183194

184195
using ssize_t = Py_ssize_t;
@@ -564,16 +575,16 @@ struct nodelete { template <typename T> void operator()(T*) { } };
564575
NAMESPACE_BEGIN(detail)
565576
template <typename... Args>
566577
struct overload_cast_impl {
567-
template <typename Return>
568-
constexpr auto operator()(Return (*pf)(Args...)) const noexcept
578+
template <typename Return /*,*/ PYBIND11_NOEXCEPT_TPL_ARG>
579+
constexpr auto operator()(Return (*pf)(Args...) PYBIND11_NOEXCEPT_SPECIFIER) const noexcept
569580
-> decltype(pf) { return pf; }
570581

571-
template <typename Return, typename Class>
572-
constexpr auto operator()(Return (Class::*pmf)(Args...), std::false_type = {}) const noexcept
582+
template <typename Return, typename Class /*,*/ PYBIND11_NOEXCEPT_TPL_ARG>
583+
constexpr auto operator()(Return (Class::*pmf)(Args...) PYBIND11_NOEXCEPT_SPECIFIER, std::false_type = {}) const noexcept
573584
-> decltype(pmf) { return pmf; }
574585

575-
template <typename Return, typename Class>
576-
constexpr auto operator()(Return (Class::*pmf)(Args...) const, std::true_type) const noexcept
586+
template <typename Return, typename Class /*,*/ PYBIND11_NOEXCEPT_TPL_ARG>
587+
constexpr auto operator()(Return (Class::*pmf)(Args...) const PYBIND11_NOEXCEPT_SPECIFIER, std::true_type) const noexcept
577588
-> decltype(pmf) { return pmf; }
578589
};
579590
NAMESPACE_END(detail)

include/pybind11/functional.h

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,12 @@
1515
NAMESPACE_BEGIN(pybind11)
1616
NAMESPACE_BEGIN(detail)
1717

18-
template <typename Return, typename... Args> struct type_caster<std::function<Return(Args...)>> {
19-
typedef std::function<Return(Args...)> type;
20-
typedef typename std::conditional<std::is_same<Return, void>::value, void_type, Return>::type retval_type;
18+
template <typename Return, typename... Args /*,*/ PYBIND11_NOEXCEPT_TPL_ARG>
19+
struct type_caster<std::function<Return(Args...) PYBIND11_NOEXCEPT_SPECIFIER>> {
20+
using type = std::function<Return(Args...) PYBIND11_NOEXCEPT_SPECIFIER>;
21+
using retval_type = conditional_t<std::is_same<Return, void>::value, void_type, Return>;
22+
using function_type = Return (*) (Args...) PYBIND11_NOEXCEPT_SPECIFIER;
23+
2124
public:
2225
bool load(handle src_, bool) {
2326
if (src_.is_none())
@@ -38,10 +41,9 @@ template <typename Return, typename... Args> struct type_caster<std::function<Re
3841
if (PyCFunction_Check(src_.ptr())) {
3942
auto c = reinterpret_borrow<capsule>(PyCFunction_GetSelf(src_.ptr()));
4043
auto rec = (function_record *) c;
41-
using FunctionType = Return (*) (Args...);
4244

43-
if (rec && rec->is_stateless && rec->data[1] == &typeid(FunctionType)) {
44-
struct capture { FunctionType f; };
45+
if (rec && rec->is_stateless && rec->data[1] == &typeid(function_type)) {
46+
struct capture { function_type f; };
4547
value = ((capture *) &rec->data)->f;
4648
return true;
4749
}
@@ -62,7 +64,7 @@ template <typename Return, typename... Args> struct type_caster<std::function<Re
6264
if (!f_)
6365
return none().inc_ref();
6466

65-
auto result = f_.template target<Return (*)(Args...)>();
67+
auto result = f_.template target<function_type>();
6668
if (result)
6769
return cpp_function(*result, policy).release();
6870
else

include/pybind11/numpy.h

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1145,13 +1145,15 @@ template <typename T, int Flags> struct handle_type_name<array_t<T, Flags>> {
11451145

11461146
NAMESPACE_END(detail)
11471147

1148-
template <typename Func, typename Return, typename... Args>
1149-
detail::vectorize_helper<Func, Return, Args...> vectorize(const Func &f, Return (*) (Args ...)) {
1148+
template <typename Func, typename Return, typename... Args /*,*/ PYBIND11_NOEXCEPT_TPL_ARG>
1149+
detail::vectorize_helper<Func, Return, Args...>
1150+
vectorize(const Func &f, Return (*) (Args ...) PYBIND11_NOEXCEPT_SPECIFIER) {
11501151
return detail::vectorize_helper<Func, Return, Args...>(f);
11511152
}
11521153

1153-
template <typename Return, typename... Args>
1154-
detail::vectorize_helper<Return (*) (Args ...), Return, Args...> vectorize(Return (*f) (Args ...)) {
1154+
template <typename Return, typename... Args /*,*/ PYBIND11_NOEXCEPT_TPL_ARG>
1155+
detail::vectorize_helper<Return (*) (Args ...) PYBIND11_NOEXCEPT_SPECIFIER, Return, Args...>
1156+
vectorize(Return (*f) (Args ...) PYBIND11_NOEXCEPT_SPECIFIER) {
11551157
return vectorize<Return (*) (Args ...), Return, Args...>(f, f);
11561158
}
11571159

include/pybind11/pybind11.h

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ class cpp_function : public function {
4444
cpp_function() { }
4545

4646
/// Construct a cpp_function from a vanilla function pointer
47-
template <typename Return, typename... Args, typename... Extra>
48-
cpp_function(Return (*f)(Args...), const Extra&... extra) {
47+
template <typename Return, typename... Args, typename... Extra /*,*/ PYBIND11_NOEXCEPT_TPL_ARG>
48+
cpp_function(Return (*f)(Args...) PYBIND11_NOEXCEPT_SPECIFIER, const Extra&... extra) {
4949
initialize(f, f, extra...);
5050
}
5151

@@ -57,17 +57,17 @@ class cpp_function : public function {
5757
}
5858

5959
/// Construct a cpp_function from a class method (non-const)
60-
template <typename Return, typename Class, typename... Arg, typename... Extra>
61-
cpp_function(Return (Class::*f)(Arg...), const Extra&... extra) {
60+
template <typename Return, typename Class, typename... Arg, typename... Extra /*,*/ PYBIND11_NOEXCEPT_TPL_ARG>
61+
cpp_function(Return (Class::*f)(Arg...) PYBIND11_NOEXCEPT_SPECIFIER, const Extra&... extra) {
6262
initialize([f](Class *c, Arg... args) -> Return { return (c->*f)(args...); },
63-
(Return (*) (Class *, Arg...)) nullptr, extra...);
63+
(Return (*) (Class *, Arg...) PYBIND11_NOEXCEPT_SPECIFIER) nullptr, extra...);
6464
}
6565

6666
/// Construct a cpp_function from a class method (const)
67-
template <typename Return, typename Class, typename... Arg, typename... Extra>
68-
cpp_function(Return (Class::*f)(Arg...) const, const Extra&... extra) {
67+
template <typename Return, typename Class, typename... Arg, typename... Extra /*,*/ PYBIND11_NOEXCEPT_TPL_ARG>
68+
cpp_function(Return (Class::*f)(Arg...) const PYBIND11_NOEXCEPT_SPECIFIER, const Extra&... extra) {
6969
initialize([f](const Class *c, Arg... args) -> Return { return (c->*f)(args...); },
70-
(Return (*)(const Class *, Arg ...)) nullptr, extra...);
70+
(Return (*)(const Class *, Arg ...) PYBIND11_NOEXCEPT_SPECIFIER) nullptr, extra...);
7171
}
7272

7373
/// Return the function name
@@ -111,8 +111,8 @@ class cpp_function : public function {
111111
}
112112

113113
/// Special internal constructor for functors, lambda functions, etc.
114-
template <typename Func, typename Return, typename... Args, typename... Extra>
115-
void initialize(Func &&f, Return (*)(Args...), const Extra&... extra) {
114+
template <typename Func, typename Return, typename... Args, typename... Extra /*,*/ PYBIND11_NOEXCEPT_TPL_ARG>
115+
void initialize(Func &&f, Return (*)(Args...) PYBIND11_NOEXCEPT_SPECIFIER, const Extra&... extra) {
116116
static_assert(detail::expected_num_args<Extra...>(sizeof...(Args)),
117117
"The number of named arguments does not match the function signature");
118118

@@ -163,7 +163,7 @@ class cpp_function : public function {
163163
if (cast_in::has_kwargs) rec->has_kwargs = true;
164164

165165
/* Stash some additional information used by an important optimization in 'functional.h' */
166-
using FunctionType = Return (*)(Args...);
166+
using FunctionType = Return (*)(Args...) PYBIND11_NOEXCEPT_SPECIFIER;
167167
constexpr bool is_function_ptr =
168168
std::is_convertible<Func, FunctionType>::value &&
169169
sizeof(capture) == sizeof(void *);

tests/test_constants_and_functions.cpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,26 @@ std::string print_bytes(py::bytes bytes) {
4141
return ret;
4242
}
4343

44+
// Test that we properly handle C++17 exception specifiers (which are part of the function signature
45+
// in C++17). These should all still work before C++17, but don't affect the function signature.
46+
namespace test_exc_sp {
47+
int f1(int x) noexcept { return x+1; }
48+
int f2(int x) noexcept(true) { return x+2; }
49+
int f3(int x) noexcept(false) { return x+3; }
50+
int f4(int x) throw() { return x+4; } // Deprecated equivalent to noexcept(true)
51+
struct C {
52+
int m1(int x) noexcept { return x-1; }
53+
int m2(int x) const noexcept { return x-2; }
54+
int m3(int x) noexcept(true) { return x-3; }
55+
int m4(int x) const noexcept(true) { return x-4; }
56+
int m5(int x) noexcept(false) { return x-5; }
57+
int m6(int x) const noexcept(false) { return x-6; }
58+
int m7(int x) throw() { return x-7; }
59+
int m8(int x) const throw() { return x-8; }
60+
};
61+
}
62+
63+
4464
test_initializer constants_and_functions([](py::module &m) {
4565
m.attr("some_constant") = py::int_(14);
4666

@@ -63,4 +83,22 @@ test_initializer constants_and_functions([](py::module &m) {
6383

6484
m.def("return_bytes", &return_bytes);
6585
m.def("print_bytes", &print_bytes);
86+
87+
using namespace test_exc_sp;
88+
py::module m2 = m.def_submodule("exc_sp");
89+
py::class_<C>(m2, "C")
90+
.def(py::init<>())
91+
.def("m1", &C::m1)
92+
.def("m2", &C::m2)
93+
.def("m3", &C::m3)
94+
.def("m4", &C::m4)
95+
.def("m5", &C::m5)
96+
.def("m6", &C::m6)
97+
.def("m7", &C::m7)
98+
.def("m8", &C::m8)
99+
;
100+
m2.def("f1", f1);
101+
m2.def("f2", f2);
102+
m2.def("f3", f3);
103+
m2.def("f4", f4);
66104
});

tests/test_constants_and_functions.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,21 @@ def test_bytes():
2222
from pybind11_tests import return_bytes, print_bytes
2323

2424
assert print_bytes(return_bytes()) == "bytes[1 0 2 0]"
25+
26+
def test_exception_specifiers():
27+
from pybind11_tests.exc_sp import C, f1, f2, f3, f4
28+
29+
c = C()
30+
assert c.m1(2) == 1
31+
assert c.m2(3) == 1
32+
assert c.m3(5) == 2
33+
assert c.m4(7) == 3
34+
assert c.m5(10) == 5
35+
assert c.m6(14) == 8
36+
assert c.m7(20) == 13
37+
assert c.m8(29) == 21
38+
39+
assert f1(33) == 34
40+
assert f2(53) == 55
41+
assert f3(86) == 89
42+
assert f4(140) == 144

0 commit comments

Comments
 (0)