Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 37 additions & 2 deletions include/pybind11/detail/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -1056,14 +1056,30 @@ struct strip_function_object {
using type = typename remove_class<decltype(&F::operator())>::type;
};

// Strip noexcept from a free function type (C++17: noexcept is part of the type).
template <typename T>
struct remove_noexcept {
using type = T;
};
#ifdef __cpp_noexcept_function_type
template <typename R, typename... A>
struct remove_noexcept<R(A...) noexcept> {
using type = R(A...);
};
#endif
template <typename T>
using remove_noexcept_t = typename remove_noexcept<T>::type;

// Extracts the function signature from a function, function pointer or lambda.
// Strips noexcept from the result so that factory/pickle_factory partial specializations
// (which match plain Return(Args...)) work correctly with noexcept callables (issue #2234).
template <typename Function, typename F = remove_reference_t<Function>>
using function_signature_t = conditional_t<
using function_signature_t = remove_noexcept_t<conditional_t<
std::is_function<F>::value,
F,
typename conditional_t<std::is_pointer<F>::value || std::is_member_pointer<F>::value,
std::remove_pointer<F>,
strip_function_object<F>>::type>;
strip_function_object<F>>::type>>;

/// Returns true if the type looks like a lambda: that is, isn't a function, pointer or member
/// pointer. Note that this can catch all sorts of other things, too; this is intended to be used
Expand Down Expand Up @@ -1212,6 +1228,25 @@ struct overload_cast_impl {
-> decltype(pmf) {
return pmf;
}

#ifdef __cpp_noexcept_function_type
template <typename Return>
constexpr auto operator()(Return (*pf)(Args...) noexcept) const noexcept -> decltype(pf) {
return pf;
}

template <typename Return, typename Class>
constexpr auto operator()(Return (Class::*pmf)(Args...) noexcept,
std::false_type = {}) const noexcept -> decltype(pmf) {
return pmf;
}

template <typename Return, typename Class>
constexpr auto operator()(Return (Class::*pmf)(Args...) const noexcept,
std::true_type) const noexcept -> decltype(pmf) {
return pmf;
}
#endif
};
PYBIND11_NAMESPACE_END(detail)

Expand Down
28 changes: 28 additions & 0 deletions include/pybind11/numpy.h
Original file line number Diff line number Diff line change
Expand Up @@ -2327,4 +2327,32 @@ Helper vectorize(Return (Class::*f)(Args...) const) {
return Helper(std::mem_fn(f));
}

#ifdef __cpp_noexcept_function_type
// Vectorize a class method (non-const, noexcept):
template <typename Return,
typename Class,
typename... Args,
typename Helper = detail::vectorize_helper<
decltype(std::mem_fn(std::declval<Return (Class::*)(Args...) noexcept>())),
Return,
Class *,
Args...>>
Helper vectorize(Return (Class::*f)(Args...) noexcept) {
return Helper(std::mem_fn(f));
}

// Vectorize a class method (const, noexcept):
template <typename Return,
typename Class,
typename... Args,
typename Helper = detail::vectorize_helper<
decltype(std::mem_fn(std::declval<Return (Class::*)(Args...) const noexcept>())),
Return,
const Class *,
Args...>>
Helper vectorize(Return (Class::*f)(Args...) const noexcept) {
return Helper(std::mem_fn(f));
}
#endif

PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)
109 changes: 109 additions & 0 deletions include/pybind11/pybind11.h
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,48 @@ class cpp_function : public function {
extra...);
}

#ifdef __cpp_noexcept_function_type
/// Construct a cpp_function from a class method (non-const, no ref-qualifier, noexcept)
template <typename Return, typename Class, typename... Arg, typename... Extra>
// NOLINTNEXTLINE(google-explicit-constructor)
cpp_function(Return (Class::*f)(Arg...) noexcept, const Extra &...extra) {
initialize(
[f](Class *c, Arg... args) -> Return { return (c->*f)(std::forward<Arg>(args)...); },
(Return (*)(Class *, Arg...)) nullptr,
extra...);
}

/// Construct a cpp_function from a class method (non-const, lvalue ref-qualifier, noexcept)
template <typename Return, typename Class, typename... Arg, typename... Extra>
// NOLINTNEXTLINE(google-explicit-constructor)
cpp_function(Return (Class::*f)(Arg...) & noexcept, const Extra &...extra) {
initialize(
[f](Class *c, Arg... args) -> Return { return (c->*f)(std::forward<Arg>(args)...); },
(Return (*)(Class *, Arg...)) nullptr,
extra...);
}

/// Construct a cpp_function from a class method (const, no ref-qualifier, noexcept)
template <typename Return, typename Class, typename... Arg, typename... Extra>
// NOLINTNEXTLINE(google-explicit-constructor)
cpp_function(Return (Class::*f)(Arg...) const noexcept, const Extra &...extra) {
initialize([f](const Class *c,
Arg... args) -> Return { return (c->*f)(std::forward<Arg>(args)...); },
(Return (*)(const Class *, Arg...)) nullptr,
extra...);
}

/// Construct a cpp_function from a class method (const, lvalue ref-qualifier, noexcept)
template <typename Return, typename Class, typename... Arg, typename... Extra>
// NOLINTNEXTLINE(google-explicit-constructor)
cpp_function(Return (Class::*f)(Arg...) const & noexcept, const Extra &...extra) {
initialize([f](const Class *c,
Arg... args) -> Return { return (c->*f)(std::forward<Arg>(args)...); },
(Return (*)(const Class *, Arg...)) nullptr,
extra...);
}
#endif

/// Return the function name
object name() const { return attr("__name__"); }

Expand Down Expand Up @@ -1905,6 +1947,61 @@ auto method_adaptor(Return (Class::*pmf)(Args...) const) -> Return (Derived::*)(
return pmf;
}

template <typename Derived, typename Return, typename Class, typename... Args>
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe able to massively simplify this with a macro...

auto method_adaptor(Return (Class::*pmf)(Args...) &) -> Return (Derived::*)(Args...) & {
static_assert(
detail::is_accessible_base_of<Class, Derived>::value,
"Cannot bind an inaccessible base class method; use a lambda definition instead");
return pmf;
}

template <typename Derived, typename Return, typename Class, typename... Args>
auto method_adaptor(Return (Class::*pmf)(Args...) const &)
-> Return (Derived::*)(Args...) const & {
static_assert(
detail::is_accessible_base_of<Class, Derived>::value,
"Cannot bind an inaccessible base class method; use a lambda definition instead");
return pmf;
}

#ifdef __cpp_noexcept_function_type
template <typename Derived, typename Return, typename Class, typename... Args>
auto method_adaptor(Return (Class::*pmf)(Args...) noexcept)
-> Return (Derived::*)(Args...) noexcept {
static_assert(
detail::is_accessible_base_of<Class, Derived>::value,
"Cannot bind an inaccessible base class method; use a lambda definition instead");
return pmf;
}

template <typename Derived, typename Return, typename Class, typename... Args>
auto method_adaptor(Return (Class::*pmf)(Args...) const noexcept)
-> Return (Derived::*)(Args...) const noexcept {
static_assert(
detail::is_accessible_base_of<Class, Derived>::value,
"Cannot bind an inaccessible base class method; use a lambda definition instead");
return pmf;
}

template <typename Derived, typename Return, typename Class, typename... Args>
auto method_adaptor(Return (Class::*pmf)(Args...) & noexcept)
-> Return (Derived::*)(Args...) & noexcept {
static_assert(
detail::is_accessible_base_of<Class, Derived>::value,
"Cannot bind an inaccessible base class method; use a lambda definition instead");
return pmf;
}

template <typename Derived, typename Return, typename Class, typename... Args>
auto method_adaptor(Return (Class::*pmf)(Args...) const & noexcept)
-> Return (Derived::*)(Args...) const & noexcept {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I need to add tests for '&&' here i guess. Otherwise the fall through the perfect forwarding template... Sigh... I'll try construct some tests later I guess.

static_assert(
detail::is_accessible_base_of<Class, Derived>::value,
"Cannot bind an inaccessible base class method; use a lambda definition instead");
return pmf;
}
#endif

PYBIND11_NAMESPACE_BEGIN(detail)

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

#ifdef __cpp_noexcept_function_type
template <typename Return, typename Class, typename... Args>
class_ &def_buffer(Return (Class::*func)(Args...) noexcept) {
return def_buffer([func](type &obj) { return (obj.*func)(); });
}

template <typename Return, typename Class, typename... Args>
class_ &def_buffer(Return (Class::*func)(Args...) const noexcept) {
return def_buffer([func](const type &obj) { return (obj.*func)(); });
}
#endif

template <typename C, typename D, typename... Extra>
class_ &def_readwrite(const char *name, D C::*pm, const Extra &...extra) {
static_assert(std::is_same<C, type>::value || std::is_base_of<C, type>::value,
Expand Down
46 changes: 46 additions & 0 deletions tests/test_buffers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -439,4 +439,50 @@ TEST_SUBMODULE(buffers, m) {
PyBuffer_Release(&buffer);
return result;
});

// test_noexcept_def_buffer (issue #2234)
// def_buffer(Return (Class::*)(Args...) noexcept) and
// def_buffer(Return (Class::*)(Args...) const noexcept) must compile and work correctly.
struct OneDBuffer {
// Declare m_data before m_n to match initialiser list order below.
float *m_data;
py::ssize_t m_n;
explicit OneDBuffer(py::ssize_t n) : m_data(new float[(size_t) n]()), m_n(n) {}
~OneDBuffer() { delete[] m_data; }
// Exercises def_buffer(Return (Class::*)(Args...) noexcept)
py::buffer_info get_buffer() noexcept {
return py::buffer_info(m_data,
sizeof(float),
py::format_descriptor<float>::format(),
1,
{m_n},
{(py::ssize_t) sizeof(float)});
}
};

// non-const noexcept member function form
py::class_<OneDBuffer>(m, "OneDBuffer", py::buffer_protocol())
.def(py::init<py::ssize_t>())
.def_buffer(&OneDBuffer::get_buffer);

// const noexcept member function form (separate class to avoid ambiguity)
struct OneDBufferConst {
float *m_data;
py::ssize_t m_n;
explicit OneDBufferConst(py::ssize_t n) : m_data(new float[(size_t) n]()), m_n(n) {}
~OneDBufferConst() { delete[] m_data; }
// Exercises def_buffer(Return (Class::*)(Args...) const noexcept)
py::buffer_info get_buffer() const noexcept {
return py::buffer_info(m_data,
sizeof(float),
py::format_descriptor<float>::format(),
1,
{m_n},
{(py::ssize_t) sizeof(float)},
/*readonly=*/true);
}
};
py::class_<OneDBufferConst>(m, "OneDBufferConst", py::buffer_protocol())
.def(py::init<py::ssize_t>())
.def_buffer(&OneDBufferConst::get_buffer);
}
22 changes: 22 additions & 0 deletions tests/test_buffers.py
Original file line number Diff line number Diff line change
Expand Up @@ -399,3 +399,25 @@ def check_strides(mat):
m.get_py_buffer(dmat, m.PyBUF_ANY_CONTIGUOUS)
with pytest.raises(expected_exception):
m.get_py_buffer(dmat, m.PyBUF_F_CONTIGUOUS)


def test_noexcept_def_buffer():
"""Test issue #2234: def_buffer with noexcept member function pointers.

Covers both new def_buffer specialisations:
- def_buffer(Return (Class::*)(Args...) noexcept)
- def_buffer(Return (Class::*)(Args...) const noexcept)
"""
# non-const noexcept member function form
buf = m.OneDBuffer(5)
arr = np.frombuffer(buf, dtype=np.float32)
assert arr.shape == (5,)
arr[2] = 3.14
arr2 = np.frombuffer(buf, dtype=np.float32)
assert arr2[2] == pytest.approx(3.14)

# const noexcept member function form
cbuf = m.OneDBufferConst(4)
carr = np.frombuffer(cbuf, dtype=np.float32)
assert carr.shape == (4,)
assert carr.flags["WRITEABLE"] is False
Loading
Loading