Support C++17 aligned new statement (#1582)
* Support C++17 aligned new statement
This patch makes pybind11 aware of nonstandard alignment requirements in
bound types and passes on this information to C++17 aligned 'new'
operator. Pre-C++17, the behavior is unchanged.
diff --git a/include/pybind11/attr.h b/include/pybind11/attr.h
index 985bc57..8732cfe 100644
--- a/include/pybind11/attr.h
+++ b/include/pybind11/attr.h
@@ -214,11 +214,14 @@
/// How large is the underlying C++ type?
size_t type_size = 0;
+ /// What is the alignment of the underlying C++ type?
+ size_t type_align = 0;
+
/// How large is the type's holder?
size_t holder_size = 0;
/// The global operator new can be overridden with a class-specific variant
- void *(*operator_new)(size_t) = ::operator new;
+ void *(*operator_new)(size_t) = nullptr;
/// Function pointer to class_<..>::init_instance
void (*init_instance)(instance *, const void *) = nullptr;
diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h
index a21d6ba..2dec23c 100644
--- a/include/pybind11/cast.h
+++ b/include/pybind11/cast.h
@@ -571,7 +571,17 @@
// Lazy allocation for unallocated values:
if (vptr == nullptr) {
auto *type = v_h.type ? v_h.type : typeinfo;
- vptr = type->operator_new(type->type_size);
+ if (type->operator_new) {
+ vptr = type->operator_new(type->type_size);
+ } else {
+ #if defined(PYBIND11_CPP17)
+ if (type->type_align > __STDCPP_DEFAULT_NEW_ALIGNMENT__)
+ vptr = ::operator new(type->type_size,
+ (std::align_val_t) type->type_align);
+ else
+ #endif
+ vptr = ::operator new(type->type_size);
+ }
}
value = vptr;
}
diff --git a/include/pybind11/detail/internals.h b/include/pybind11/detail/internals.h
index 78d4afe..ad34415 100644
--- a/include/pybind11/detail/internals.h
+++ b/include/pybind11/detail/internals.h
@@ -116,7 +116,7 @@
struct type_info {
PyTypeObject *type;
const std::type_info *cpptype;
- size_t type_size, holder_size_in_ptrs;
+ size_t type_size, type_align, holder_size_in_ptrs;
void *(*operator_new)(size_t);
void (*init_instance)(instance *, const void *);
void (*dealloc)(value_and_holder &v_h);
@@ -138,7 +138,7 @@
};
/// Tracks the `internals` and `type_info` ABI version independent of the main library version
-#define PYBIND11_INTERNALS_VERSION 2
+#define PYBIND11_INTERNALS_VERSION 3
#if defined(WITH_THREAD)
# define PYBIND11_INTERNALS_KIND ""
diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h
index 99a1e0a..e45f1a0 100644
--- a/include/pybind11/pybind11.h
+++ b/include/pybind11/pybind11.h
@@ -896,6 +896,7 @@
tinfo->type = (PyTypeObject *) m_ptr;
tinfo->cpptype = rec.type;
tinfo->type_size = rec.type_size;
+ tinfo->type_align = rec.type_align;
tinfo->operator_new = rec.operator_new;
tinfo->holder_size_in_ptrs = size_in_ptrs(rec.holder_size);
tinfo->init_instance = rec.init_instance;
@@ -987,11 +988,21 @@
: std::true_type { };
/// Call class-specific delete if it exists or global otherwise. Can also be an overload set.
template <typename T, enable_if_t<has_operator_delete<T>::value, int> = 0>
-void call_operator_delete(T *p, size_t) { T::operator delete(p); }
+void call_operator_delete(T *p, size_t, size_t) { T::operator delete(p); }
template <typename T, enable_if_t<!has_operator_delete<T>::value && has_operator_delete_size<T>::value, int> = 0>
-void call_operator_delete(T *p, size_t s) { T::operator delete(p, s); }
+void call_operator_delete(T *p, size_t s, size_t) { T::operator delete(p, s); }
-inline void call_operator_delete(void *p, size_t) { ::operator delete(p); }
+inline void call_operator_delete(void *p, size_t s, size_t a) {
+ (void)s; (void)a;
+#if defined(PYBIND11_CPP17)
+ if (a > __STDCPP_DEFAULT_NEW_ALIGNMENT__)
+ ::operator delete(p, s, std::align_val_t(a));
+ else
+ ::operator delete(p, s);
+#else
+ ::operator delete(p);
+#endif
+}
NAMESPACE_END(detail)
@@ -1054,6 +1065,7 @@
record.name = name;
record.type = &typeid(type);
record.type_size = sizeof(conditional_t<has_alias, type_alias, type>);
+ record.type_align = alignof(conditional_t<has_alias, type_alias, type>&);
record.holder_size = sizeof(holder_type);
record.init_instance = init_instance;
record.dealloc = dealloc;
@@ -1329,7 +1341,10 @@
v_h.set_holder_constructed(false);
}
else {
- detail::call_operator_delete(v_h.value_ptr<type>(), v_h.type->type_size);
+ detail::call_operator_delete(v_h.value_ptr<type>(),
+ v_h.type->type_size,
+ v_h.type->type_align
+ );
}
v_h.value_ptr() = nullptr;
}
diff --git a/tests/test_class.cpp b/tests/test_class.cpp
index 9ed1f50..499d0cc 100644
--- a/tests/test_class.cpp
+++ b/tests/test_class.cpp
@@ -12,6 +12,10 @@
#include "local_bindings.h"
#include <pybind11/stl.h>
+#if defined(_MSC_VER)
+# pragma warning(disable: 4324) // warning C4324: structure was padded due to alignment specifier
+#endif
+
// test_brace_initialization
struct NoBraceInitialization {
NoBraceInitialization(std::vector<int> v) : vec{std::move(v)} {}
@@ -354,6 +358,15 @@
[](StringWrapper) -> NotRegistered { return {}; });
py::class_<StringWrapper>(m, "StringWrapper").def(py::init<std::string>());
py::implicitly_convertible<std::string, StringWrapper>();
+
+ #if defined(PYBIND11_CPP17)
+ struct alignas(1024) Aligned {
+ std::uintptr_t ptr() const { return (uintptr_t) this; }
+ };
+ py::class_<Aligned>(m, "Aligned")
+ .def(py::init<>())
+ .def("ptr", &Aligned::ptr);
+ #endif
}
template <int N> class BreaksBase { public: virtual ~BreaksBase() = default; };
diff --git a/tests/test_class.py b/tests/test_class.py
index 4a488ab..ed63ca8 100644
--- a/tests/test_class.py
+++ b/tests/test_class.py
@@ -273,3 +273,9 @@
m.test_error_after_conversions("hello")
assert str(exc_info.value).startswith(
"Unable to convert function return value to a Python type!")
+
+
+def test_aligned():
+ if hasattr(m, "Aligned"):
+ p = m.Aligned().ptr()
+ assert p % 1024 == 0