Make argument_vector re-usable for other types.

This commit is contained in:
b-pass
2025-12-23 23:17:06 -05:00
parent 799f591ec3
commit ef662f6f17

View File

@@ -63,53 +63,11 @@ union inline_array_or_vector {
heap_vector(std::size_t count, VectorT value) : vec(count, value) {}
};
static_assert(std::is_nothrow_move_assignable_v<ArrayT>, "this class is not exception safe");
inline_array iarray;
heap_vector hvector;
static_assert(std::is_trivially_move_constructible<ArrayT>::value,
"ArrayT must be trivially move constructible");
static_assert(std::is_trivially_destructible<ArrayT>::value,
"ArrayT must be trivially destructible");
inline_array_or_vector() : iarray() {}
~inline_array_or_vector() {
if (!is_inline()) {
hvector.~heap_vector();
}
}
// Disable copy ctor and assignment.
inline_array_or_vector(const inline_array_or_vector &) = delete;
inline_array_or_vector &operator=(const inline_array_or_vector &) = delete;
inline_array_or_vector(inline_array_or_vector &&rhs) noexcept {
if (rhs.is_inline()) {
std::memcpy(&iarray, &rhs.iarray, sizeof(iarray));
} else {
new (&hvector) heap_vector(std::move(rhs.hvector));
}
assert(is_inline() == rhs.is_inline());
}
inline_array_or_vector &operator=(inline_array_or_vector &&rhs) noexcept {
if (this == &rhs) {
return *this;
}
if (rhs.is_inline()) {
if (!is_inline()) {
hvector.~heap_vector();
}
std::memcpy(&iarray, &rhs.iarray, sizeof(iarray));
} else {
if (is_inline()) {
new (&hvector) heap_vector(std::move(rhs.hvector));
} else {
hvector = std::move(rhs.hvector);
}
}
return *this;
}
bool is_inline() const {
// It is undefined behavior to access the inactive member of a
// union directly. However, it is well-defined to reinterpret_cast any
@@ -124,20 +82,64 @@ union inline_array_or_vector {
std::memcpy(&result, reinterpret_cast<const char *>(this), sizeof(bool));
return result;
}
};
// small_vector-like container to avoid heap allocation for N or fewer
// arguments.
template <std::size_t N>
struct argument_vector {
public:
argument_vector() = default;
void destruct() {
if (!is_inline()) {
hvector.~heap_vector();
}
else if (!std::is_trivially_destructible<ArrayT>::value) {
for (size_t i = 0; i < iarray.size; ++i) {
iarray.arr[i].~ArrayT();
}
}
}
inline_array_or_vector() : iarray() {}
~inline_array_or_vector() {
destruct();
}
// Disable copy ctor and assignment.
argument_vector(const argument_vector &) = delete;
argument_vector &operator=(const argument_vector &) = delete;
argument_vector(argument_vector &&) noexcept = default;
argument_vector &operator=(argument_vector &&) noexcept = default;
inline_array_or_vector(const inline_array_or_vector &) = delete;
inline_array_or_vector &operator=(const inline_array_or_vector &) = delete;
inline_array_or_vector(inline_array_or_vector &&rhs) noexcept {
if (rhs.is_inline()) {
new (&iarray) inline_array(std::move(rhs.iarray));
} else {
new (&hvector) heap_vector(std::move(rhs.hvector));
}
assert(is_inline() == rhs.is_inline());
}
inline_array_or_vector &operator=(inline_array_or_vector &&rhs) noexcept {
if (this == &rhs) {
return *this;
}
destruct();
if (rhs.is_inline()) {
new (&iarray) inline_array(std::move(rhs.iarray));
} else {
new (&hvector) heap_vector(std::move(rhs.hvector));
}
return *this;
}
};
template <typename T, std::size_t InlineSize>
struct small_vector {
public:
small_vector() = default;
// Disable copy ctor and assignment.
small_vector(const small_vector &) = delete;
small_vector &operator=(const small_vector &) = delete;
small_vector(small_vector &&) noexcept = default;
small_vector &operator=(small_vector &&) noexcept = default;
std::size_t size() const {
if (is_inline()) {
@@ -146,7 +148,14 @@ public:
return m_repr.hvector.vec.size();
}
handle &operator[](std::size_t idx) {
T const *data() const {
if (is_inline()) {
return &m_repr.iarray.arr[0];
}
return m_repr.hvector.vec.data();
}
T &operator[](std::size_t idx) {
assert(idx < size());
if (is_inline()) {
return m_repr.iarray.arr[idx];
@@ -154,7 +163,7 @@ public:
return m_repr.hvector.vec[idx];
}
handle operator[](std::size_t idx) const {
T operator[](std::size_t idx) const {
assert(idx < size());
if (is_inline()) {
return m_repr.iarray.arr[idx];
@@ -162,28 +171,28 @@ public:
return m_repr.hvector.vec[idx];
}
void push_back(handle x) {
void push_back(T x) {
if (is_inline()) {
auto &ha = m_repr.iarray;
if (ha.size == N) {
move_to_heap_vector_with_reserved_size(N + 1);
push_back_slow_path(x);
if (ha.size == InlineSize) {
move_to_heap_vector_with_reserved_size(InlineSize + 1);
push_back_slow_path(std::move(x));
} else {
ha.arr[ha.size++] = x;
ha.arr[ha.size++] = std::move(x);
}
} else {
push_back_slow_path(x);
push_back_slow_path(std::move(x));
}
}
template <typename Arg>
void emplace_back(Arg &&x) {
push_back(handle(x));
push_back(T(x));
}
void reserve(std::size_t sz) {
if (is_inline()) {
if (sz > N) {
if (sz > InlineSize) {
move_to_heap_vector_with_reserved_size(sz);
}
} else {
@@ -192,7 +201,7 @@ public:
}
private:
using repr_type = inline_array_or_vector<handle, N>;
using repr_type = inline_array_or_vector<T, InlineSize>;
repr_type m_repr;
PYBIND11_NOINLINE void move_to_heap_vector_with_reserved_size(std::size_t reserved_size) {
@@ -201,32 +210,32 @@ private:
using heap_vector = typename repr_type::heap_vector;
heap_vector hv;
hv.vec.reserve(reserved_size);
std::copy(ha.arr.begin(), ha.arr.begin() + ha.size, std::back_inserter(hv.vec));
static_assert(std::is_nothrow_move_assignable_v<T>, "this class is not exception safe");
std::move(ha.arr.begin(), ha.arr.begin() + ha.size, std::back_inserter(hv.vec));
new (&m_repr.hvector) heap_vector(std::move(hv));
}
PYBIND11_NOINLINE void push_back_slow_path(handle x) { m_repr.hvector.vec.push_back(x); }
PYBIND11_NOINLINE void push_back_slow_path(T x) { m_repr.hvector.vec.push_back(std::move(x)); }
PYBIND11_NOINLINE void reserve_slow_path(std::size_t sz) { m_repr.hvector.vec.reserve(sz); }
bool is_inline() const { return m_repr.is_inline(); }
};
// small_vector-like container to avoid heap allocation for N or fewer
// arguments.
// Container to avoid heap allocation for kRequestedInlineSize or fewer booleans.
template <std::size_t kRequestedInlineSize>
struct args_convert_vector {
struct small_vector<bool, kRequestedInlineSize> {
private:
public:
args_convert_vector() = default;
small_vector() = default;
// Disable copy ctor and assignment.
args_convert_vector(const args_convert_vector &) = delete;
args_convert_vector &operator=(const args_convert_vector &) = delete;
args_convert_vector(args_convert_vector &&) noexcept = default;
args_convert_vector &operator=(args_convert_vector &&) noexcept = default;
small_vector(const small_vector &) = delete;
small_vector &operator=(const small_vector &) = delete;
small_vector(small_vector &&) noexcept = default;
small_vector &operator=(small_vector &&) noexcept = default;
args_convert_vector(std::size_t count, bool value) {
small_vector(std::size_t count, bool value) {
if (count > kInlineSize) {
new (&m_repr.hvector) typename repr_type::heap_vector(count, value);
} else {
@@ -284,7 +293,7 @@ public:
}
}
void swap(args_convert_vector &rhs) noexcept { std::swap(m_repr, rhs.m_repr); }
void swap(small_vector &rhs) noexcept { std::swap(m_repr, rhs.m_repr); }
private:
struct WordAndBitIndex {
@@ -326,5 +335,13 @@ private:
bool is_inline() const { return m_repr.is_inline(); }
};
// Container to avoid heap allocation for N or fewer arguments.
template <size_t N>
using argument_vector = small_vector<handle, N>;
// Container to avoid heap allocation for N or fewer booleans.
template <size_t N>
using args_convert_vector = small_vector<bool, N>;
PYBIND11_NAMESPACE_END(detail)
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)