Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJacques Lucke <jacques@blender.org>2022-03-18 12:57:45 +0300
committerJacques Lucke <jacques@blender.org>2022-03-18 12:57:45 +0300
commit2252bc6a5527cd7360d1ccfe7a2d1bc640a8dfa6 (patch)
treebb6f8921b9977dc2c387b750a29aa6ded06ec5f9 /source/blender/blenlib
parent7509a741166c97f5f87ed9e580eef6f99eb1e62a (diff)
BLI: move CPPType to blenlib
For more detail about `CPPType`, see `BLI_cpp_type.hh` and D14367. Differential Revision: https://developer.blender.org/D14367
Diffstat (limited to 'source/blender/blenlib')
-rw-r--r--source/blender/blenlib/BLI_cpp_type.hh654
-rw-r--r--source/blender/blenlib/BLI_cpp_type_make.hh250
-rw-r--r--source/blender/blenlib/CMakeLists.txt4
-rw-r--r--source/blender/blenlib/intern/cpp_type.cc23
-rw-r--r--source/blender/blenlib/tests/BLI_cpp_type_test.cc326
5 files changed, 1257 insertions, 0 deletions
diff --git a/source/blender/blenlib/BLI_cpp_type.hh b/source/blender/blenlib/BLI_cpp_type.hh
new file mode 100644
index 00000000000..ae6a87b4b68
--- /dev/null
+++ b/source/blender/blenlib/BLI_cpp_type.hh
@@ -0,0 +1,654 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#pragma once
+
+/** \file
+ * \ingroup bli
+ *
+ * The `CPPType` class allows working with arbitrary C++ types in a generic way. An instance of
+ * #CPPType wraps exactly one type like `int` or `std::string`.
+ *
+ * With #CPPType one can write generic data structures and algorithms. That is similar to what C++
+ * templates allow. The difference is that when using templates, the types have to be known at
+ * compile time and the code has to be instantiated multiple times. On the other hand, when using
+ * #CPPType, the data type only has to be known at run-time, and the code only has to be compiled
+ * once. Whether #CPPType or classic c++ templates should be used depends on the context:
+ * - If the data type is not known at run-time, #CPPType should be used.
+ * - If the data type is known to be one of a few, it depends on how performance sensitive the code
+ * is.
+ * - If it it's a small hot loop, a template can be used to optimize for every type (at the
+ * cost of longer compile time, a larger binary and the complexity that comes from using
+ * templates).
+ * - If the code is not performance sensitive, it usually makes sense to use #CPPType instead.
+ * - Sometimes a combination can make sense. Optimized code can be be generated at compile-time for
+ * some types, while there is a fallback code path using #CPPType for all other types.
+ *
+ * Under some circumstances, #CPPType serves a similar role as #std::type_info. However, #CPPType
+ * has much more utility because it contains methods for actually working with instances of the
+ * type.
+ *
+ * Every type has a size and an alignment. Every function dealing with C++ types in a generic way,
+ * has to make sure that alignment rules are followed. The methods provided by a #CPPType instance
+ * will check for correct alignment as well.
+ *
+ * Every type has a name that is for debugging purposes only. It should not be used as identifier.
+ *
+ * To check if two instances of #CPPType represent the same type, only their pointers have to be
+ * compared. Any C++ type has at most one corresponding #CPPType instance.
+ *
+ * A #CPPType instance comes with many methods that allow dealing with types in a generic way. Most
+ * methods come in three variants. Using the default-construct methods as an example:
+ * - `default_construct(void *ptr)`:
+ * Constructs a single instance of that type at the given pointer.
+ * - `default_construct_n(void *ptr, int64_t n)`:
+ * Constructs n instances of that type in an array that starts at the given pointer.
+ * - `default_construct_indices(void *ptr, IndexMask mask)`:
+ * Constructs multiple instances of that type in an array that starts at the given pointer.
+ * Only the indices referenced by `mask` will by constructed.
+ *
+ * In some cases default-construction does nothing (e.g. for trivial types like int). The
+ * `default_value` method provides some default value anyway that can be copied instead. What the
+ * default value is, depends on the type. Usually it is something like 0 or an empty string.
+ *
+ *
+ * Implementation Considerations
+ * -----------------------------
+ *
+ * Concepts like inheritance are currently not captured by this system. This is not because it is
+ * not possible, but because it was not necessary to add this complexity yet.
+ *
+ * One could also implement CPPType itself using virtual methods and a child class for every
+ * wrapped type. However, the approach used now with explicit function pointers to works better.
+ * Here are some reasons:
+ * - If CPPType would be inherited once for every used C++ type, we would get a lot of classes
+ * that would only be instanced once each.
+ * - Methods like `default_construct` that operate on a single instance have to be fast. Even this
+ * one necessary indirection using function pointers adds a lot of overhead. If all methods were
+ * virtual, there would be a second level of indirection that increases the overhead even more.
+ * - If it becomes necessary, we could pass the function pointers to C functions more easily than
+ * pointers to virtual member functions.
+ */
+
+#include "BLI_hash.hh"
+#include "BLI_index_mask.hh"
+#include "BLI_math_base.h"
+#include "BLI_string_ref.hh"
+#include "BLI_utility_mixins.hh"
+
+/**
+ * Different types support different features. Features like copy constructability can be detected
+ * automatically easily. For some features this is harder as of C++17. Those have flags in this
+ * enum and need to be determined by the programmer.
+ */
+enum class CPPTypeFlags {
+ None = 0,
+ Hashable = 1 << 0,
+ Printable = 1 << 1,
+ EqualityComparable = 1 << 2,
+
+ BasicType = Hashable | Printable | EqualityComparable,
+};
+ENUM_OPERATORS(CPPTypeFlags, CPPTypeFlags::EqualityComparable)
+
+namespace blender {
+
+/** Utility class to pass template parameters to constructor of `CPPType`. */
+template<typename T, CPPTypeFlags Flags> struct CPPTypeParam {
+};
+
+class CPPType : NonCopyable, NonMovable {
+ private:
+ int64_t size_ = 0;
+ int64_t alignment_ = 0;
+ uintptr_t alignment_mask_ = 0;
+ bool is_trivial_ = false;
+ bool is_trivially_destructible_ = false;
+ bool has_special_member_functions_ = false;
+
+ void (*default_construct_)(void *ptr) = nullptr;
+ void (*default_construct_indices_)(void *ptr, IndexMask mask) = nullptr;
+
+ void (*destruct_)(void *ptr) = nullptr;
+ void (*destruct_indices_)(void *ptr, IndexMask mask) = nullptr;
+
+ void (*copy_assign_)(const void *src, void *dst) = nullptr;
+ void (*copy_assign_indices_)(const void *src, void *dst, IndexMask mask) = nullptr;
+
+ void (*copy_construct_)(const void *src, void *dst) = nullptr;
+ void (*copy_construct_indices_)(const void *src, void *dst, IndexMask mask) = nullptr;
+
+ void (*move_assign_)(void *src, void *dst) = nullptr;
+ void (*move_assign_indices_)(void *src, void *dst, IndexMask mask) = nullptr;
+
+ void (*move_construct_)(void *src, void *dst) = nullptr;
+ void (*move_construct_indices_)(void *src, void *dst, IndexMask mask) = nullptr;
+
+ void (*relocate_assign_)(void *src, void *dst) = nullptr;
+ void (*relocate_assign_indices_)(void *src, void *dst, IndexMask mask) = nullptr;
+
+ void (*relocate_construct_)(void *src, void *dst) = nullptr;
+ void (*relocate_construct_indices_)(void *src, void *dst, IndexMask mask) = nullptr;
+
+ void (*fill_assign_indices_)(const void *value, void *dst, IndexMask mask) = nullptr;
+
+ void (*fill_construct_indices_)(const void *value, void *dst, IndexMask mask) = nullptr;
+
+ void (*print_)(const void *value, std::stringstream &ss) = nullptr;
+ bool (*is_equal_)(const void *a, const void *b) = nullptr;
+ uint64_t (*hash_)(const void *value) = nullptr;
+
+ const void *default_value_ = nullptr;
+ std::string debug_name_;
+
+ public:
+ template<typename T, CPPTypeFlags Flags> CPPType(CPPTypeParam<T, Flags>, StringRef debug_name);
+ virtual ~CPPType() = default;
+
+ /**
+ * Two types only compare equal when their pointer is equal. No two instances of CPPType for the
+ * same C++ type should be created.
+ */
+ friend bool operator==(const CPPType &a, const CPPType &b)
+ {
+ return &a == &b;
+ }
+
+ friend bool operator!=(const CPPType &a, const CPPType &b)
+ {
+ return !(&a == &b);
+ }
+
+ /**
+ * Get the `CPPType` that corresponds to a specific static type.
+ * This only works for types that actually implement the template specialization using
+ * `BLI_CPP_TYPE_MAKE`.
+ */
+ template<typename T> static const CPPType &get()
+ {
+ return CPPType::get_impl<std::remove_cv_t<T>>();
+ }
+ template<typename T> static const CPPType &get_impl();
+
+ /**
+ * Returns the name of the type for debugging purposes. This name should not be used as
+ * identifier.
+ */
+ StringRefNull name() const
+ {
+ return debug_name_;
+ }
+
+ /**
+ * Required memory in bytes for an instance of this type.
+ *
+ * C++ equivalent:
+ * `sizeof(T);`
+ */
+ int64_t size() const
+ {
+ return size_;
+ }
+
+ /**
+ * Required memory alignment for an instance of this type.
+ *
+ * C++ equivalent:
+ * alignof(T);
+ */
+ int64_t alignment() const
+ {
+ return alignment_;
+ }
+
+ /**
+ * When true, the destructor does not have to be called on this type. This can sometimes be used
+ * for optimization purposes.
+ *
+ * C++ equivalent:
+ * std::is_trivially_destructible_v<T>;
+ */
+ bool is_trivially_destructible() const
+ {
+ return is_trivially_destructible_;
+ }
+
+ /**
+ * When true, the value is like a normal C type, it can be copied around with #memcpy and does
+ * not have to be destructed.
+ *
+ * C++ equivalent:
+ * std::is_trivial_v<T>;
+ */
+ bool is_trivial() const
+ {
+ return is_trivial_;
+ }
+
+ bool is_default_constructible() const
+ {
+ return default_construct_ != nullptr;
+ }
+
+ bool is_copy_constructible() const
+ {
+ return copy_assign_ != nullptr;
+ }
+
+ bool is_move_constructible() const
+ {
+ return move_assign_ != nullptr;
+ }
+
+ bool is_destructible() const
+ {
+ return destruct_ != nullptr;
+ }
+
+ bool is_copy_assignable() const
+ {
+ return copy_assign_ != nullptr;
+ }
+
+ bool is_move_assignable() const
+ {
+ return copy_construct_ != nullptr;
+ }
+
+ bool is_printable() const
+ {
+ return print_ != nullptr;
+ }
+
+ bool is_equality_comparable() const
+ {
+ return is_equal_ != nullptr;
+ }
+
+ bool is_hashable() const
+ {
+ return hash_ != nullptr;
+ }
+
+ /**
+ * Returns true, when the type has the following functions:
+ * - Default constructor.
+ * - Copy constructor.
+ * - Move constructor.
+ * - Copy assignment operator.
+ * - Move assignment operator.
+ * - Destructor.
+ */
+ bool has_special_member_functions() const
+ {
+ return has_special_member_functions_;
+ }
+
+ /**
+ * Returns true, when the given pointer fulfills the alignment requirement of this type.
+ */
+ bool pointer_has_valid_alignment(const void *ptr) const
+ {
+ return ((uintptr_t)ptr & alignment_mask_) == 0;
+ }
+
+ bool pointer_can_point_to_instance(const void *ptr) const
+ {
+ return ptr != nullptr && pointer_has_valid_alignment(ptr);
+ }
+
+ /**
+ * Call the default constructor at the given memory location.
+ * The memory should be uninitialized before this method is called.
+ * For some trivial types (like int), this method does nothing.
+ *
+ * C++ equivalent:
+ * new (ptr) T;
+ */
+ void default_construct(void *ptr) const
+ {
+ BLI_assert(this->pointer_can_point_to_instance(ptr));
+
+ default_construct_(ptr);
+ }
+
+ void default_construct_n(void *ptr, int64_t n) const
+ {
+ this->default_construct_indices(ptr, IndexMask(n));
+ }
+
+ void default_construct_indices(void *ptr, IndexMask mask) const
+ {
+ BLI_assert(mask.size() == 0 || this->pointer_can_point_to_instance(ptr));
+
+ default_construct_indices_(ptr, mask);
+ }
+
+ /**
+ * Call the destructor on the given instance of this type. The pointer must not be nullptr.
+ *
+ * For some trivial types, this does nothing.
+ *
+ * C++ equivalent:
+ * ptr->~T();
+ */
+ void destruct(void *ptr) const
+ {
+ BLI_assert(this->pointer_can_point_to_instance(ptr));
+
+ destruct_(ptr);
+ }
+
+ void destruct_n(void *ptr, int64_t n) const
+ {
+ this->destruct_indices(ptr, IndexMask(n));
+ }
+
+ void destruct_indices(void *ptr, IndexMask mask) const
+ {
+ BLI_assert(mask.size() == 0 || this->pointer_can_point_to_instance(ptr));
+
+ destruct_indices_(ptr, mask);
+ }
+
+ /**
+ * Copy an instance of this type from src to dst.
+ *
+ * C++ equivalent:
+ * dst = src;
+ */
+ void copy_assign(const void *src, void *dst) const
+ {
+ BLI_assert(this->pointer_can_point_to_instance(src));
+ BLI_assert(this->pointer_can_point_to_instance(dst));
+
+ copy_assign_(src, dst);
+ }
+
+ void copy_assign_n(const void *src, void *dst, int64_t n) const
+ {
+ this->copy_assign_indices(src, dst, IndexMask(n));
+ }
+
+ void copy_assign_indices(const void *src, void *dst, IndexMask mask) const
+ {
+ BLI_assert(mask.size() == 0 || src != dst);
+ BLI_assert(mask.size() == 0 || this->pointer_can_point_to_instance(src));
+ BLI_assert(mask.size() == 0 || this->pointer_can_point_to_instance(dst));
+
+ copy_assign_indices_(src, dst, mask);
+ }
+
+ /**
+ * Copy an instance of this type from src to dst.
+ *
+ * The memory pointed to by dst should be uninitialized.
+ *
+ * C++ equivalent:
+ * new (dst) T(src);
+ */
+ void copy_construct(const void *src, void *dst) const
+ {
+ BLI_assert(src != dst || is_trivial_);
+ BLI_assert(this->pointer_can_point_to_instance(src));
+ BLI_assert(this->pointer_can_point_to_instance(dst));
+
+ copy_construct_(src, dst);
+ }
+
+ void copy_construct_n(const void *src, void *dst, int64_t n) const
+ {
+ this->copy_construct_indices(src, dst, IndexMask(n));
+ }
+
+ void copy_construct_indices(const void *src, void *dst, IndexMask mask) const
+ {
+ BLI_assert(mask.size() == 0 || src != dst);
+ BLI_assert(mask.size() == 0 || this->pointer_can_point_to_instance(src));
+ BLI_assert(mask.size() == 0 || this->pointer_can_point_to_instance(dst));
+
+ copy_construct_indices_(src, dst, mask);
+ }
+
+ /**
+ * Move an instance of this type from src to dst.
+ *
+ * The memory pointed to by dst should be initialized.
+ *
+ * C++ equivalent:
+ * dst = std::move(src);
+ */
+ void move_assign(void *src, void *dst) const
+ {
+ BLI_assert(this->pointer_can_point_to_instance(src));
+ BLI_assert(this->pointer_can_point_to_instance(dst));
+
+ move_assign_(src, dst);
+ }
+
+ void move_assign_n(void *src, void *dst, int64_t n) const
+ {
+ this->move_assign_indices(src, dst, IndexMask(n));
+ }
+
+ void move_assign_indices(void *src, void *dst, IndexMask mask) const
+ {
+ BLI_assert(mask.size() == 0 || src != dst);
+ BLI_assert(mask.size() == 0 || this->pointer_can_point_to_instance(src));
+ BLI_assert(mask.size() == 0 || this->pointer_can_point_to_instance(dst));
+
+ move_assign_indices_(src, dst, mask);
+ }
+
+ /**
+ * Move an instance of this type from src to dst.
+ *
+ * The memory pointed to by dst should be uninitialized.
+ *
+ * C++ equivalent:
+ * new (dst) T(std::move(src));
+ */
+ void move_construct(void *src, void *dst) const
+ {
+ BLI_assert(src != dst || is_trivial_);
+ BLI_assert(this->pointer_can_point_to_instance(src));
+ BLI_assert(this->pointer_can_point_to_instance(dst));
+
+ move_construct_(src, dst);
+ }
+
+ void move_construct_n(void *src, void *dst, int64_t n) const
+ {
+ this->move_construct_indices(src, dst, IndexMask(n));
+ }
+
+ void move_construct_indices(void *src, void *dst, IndexMask mask) const
+ {
+ BLI_assert(mask.size() == 0 || src != dst);
+ BLI_assert(mask.size() == 0 || this->pointer_can_point_to_instance(src));
+ BLI_assert(mask.size() == 0 || this->pointer_can_point_to_instance(dst));
+
+ move_construct_indices_(src, dst, mask);
+ }
+
+ /**
+ * Relocates an instance of this type from src to dst. src will point to uninitialized memory
+ * afterwards.
+ *
+ * C++ equivalent:
+ * dst = std::move(src);
+ * src->~T();
+ */
+ void relocate_assign(void *src, void *dst) const
+ {
+ BLI_assert(src != dst || is_trivial_);
+ BLI_assert(this->pointer_can_point_to_instance(src));
+ BLI_assert(this->pointer_can_point_to_instance(dst));
+
+ relocate_assign_(src, dst);
+ }
+
+ void relocate_assign_n(void *src, void *dst, int64_t n) const
+ {
+ this->relocate_assign_indices(src, dst, IndexMask(n));
+ }
+
+ void relocate_assign_indices(void *src, void *dst, IndexMask mask) const
+ {
+ BLI_assert(mask.size() == 0 || src != dst);
+ BLI_assert(mask.size() == 0 || this->pointer_can_point_to_instance(src));
+ BLI_assert(mask.size() == 0 || this->pointer_can_point_to_instance(dst));
+
+ relocate_assign_indices_(src, dst, mask);
+ }
+
+ /**
+ * Relocates an instance of this type from src to dst. src will point to uninitialized memory
+ * afterwards.
+ *
+ * C++ equivalent:
+ * new (dst) T(std::move(src))
+ * src->~T();
+ */
+ void relocate_construct(void *src, void *dst) const
+ {
+ BLI_assert(src != dst || is_trivial_);
+ BLI_assert(this->pointer_can_point_to_instance(src));
+ BLI_assert(this->pointer_can_point_to_instance(dst));
+
+ relocate_construct_(src, dst);
+ }
+
+ void relocate_construct_n(void *src, void *dst, int64_t n) const
+ {
+ this->relocate_construct_indices(src, dst, IndexMask(n));
+ }
+
+ void relocate_construct_indices(void *src, void *dst, IndexMask mask) const
+ {
+ BLI_assert(mask.size() == 0 || src != dst);
+ BLI_assert(mask.size() == 0 || this->pointer_can_point_to_instance(src));
+ BLI_assert(mask.size() == 0 || this->pointer_can_point_to_instance(dst));
+
+ relocate_construct_indices_(src, dst, mask);
+ }
+
+ /**
+ * Copy the given value to the first n elements in an array starting at dst.
+ *
+ * Other instances of the same type should live in the array before this method is called.
+ */
+ void fill_assign_n(const void *value, void *dst, int64_t n) const
+ {
+ this->fill_assign_indices(value, dst, IndexMask(n));
+ }
+
+ void fill_assign_indices(const void *value, void *dst, IndexMask mask) const
+ {
+ BLI_assert(mask.size() == 0 || this->pointer_can_point_to_instance(value));
+ BLI_assert(mask.size() == 0 || this->pointer_can_point_to_instance(dst));
+
+ fill_assign_indices_(value, dst, mask);
+ }
+
+ /**
+ * Copy the given value to the first n elements in an array starting at dst.
+ *
+ * The array should be uninitialized before this method is called.
+ */
+ void fill_construct_n(const void *value, void *dst, int64_t n) const
+ {
+ this->fill_construct_indices(value, dst, IndexMask(n));
+ }
+
+ void fill_construct_indices(const void *value, void *dst, IndexMask mask) const
+ {
+ BLI_assert(mask.size() == 0 || this->pointer_can_point_to_instance(value));
+ BLI_assert(mask.size() == 0 || this->pointer_can_point_to_instance(dst));
+
+ fill_construct_indices_(value, dst, mask);
+ }
+
+ void print(const void *value, std::stringstream &ss) const
+ {
+ BLI_assert(this->pointer_can_point_to_instance(value));
+ print_(value, ss);
+ }
+
+ std::string to_string(const void *value) const
+ {
+ std::stringstream ss;
+ this->print(value, ss);
+ return ss.str();
+ }
+
+ void print_or_default(const void *value, std::stringstream &ss, StringRef default_value) const
+ {
+ if (this->is_printable()) {
+ this->print(value, ss);
+ }
+ else {
+ ss << default_value;
+ }
+ }
+
+ bool is_equal(const void *a, const void *b) const
+ {
+ BLI_assert(this->pointer_can_point_to_instance(a));
+ BLI_assert(this->pointer_can_point_to_instance(b));
+ return is_equal_(a, b);
+ }
+
+ bool is_equal_or_false(const void *a, const void *b) const
+ {
+ if (this->is_equality_comparable()) {
+ return this->is_equal(a, b);
+ }
+ return false;
+ }
+
+ uint64_t hash(const void *value) const
+ {
+ BLI_assert(this->pointer_can_point_to_instance(value));
+ return hash_(value);
+ }
+
+ uint64_t hash_or_fallback(const void *value, uint64_t fallback_hash) const
+ {
+ if (this->is_hashable()) {
+ return this->hash(value);
+ }
+ return fallback_hash;
+ }
+
+ /**
+ * Get a pointer to a constant value of this type. The specific value depends on the type.
+ * It is usually a zero-initialized or default constructed value.
+ */
+ const void *default_value() const
+ {
+ return default_value_;
+ }
+
+ uint64_t hash() const
+ {
+ return get_default_hash(this);
+ }
+
+ void (*destruct_fn() const)(void *)
+ {
+ return destruct_;
+ }
+
+ template<typename T> bool is() const
+ {
+ return this == &CPPType::get<std::decay_t<T>>();
+ }
+};
+
+} // namespace blender
+
+/* Utility for allocating an uninitialized buffer for a single value of the given #CPPType. */
+#define BUFFER_FOR_CPP_TYPE_VALUE(type, variable_name) \
+ blender::DynamicStackBuffer<64, 64> stack_buffer_for_##variable_name((type).size(), \
+ (type).alignment()); \
+ void *variable_name = stack_buffer_for_##variable_name.buffer();
diff --git a/source/blender/blenlib/BLI_cpp_type_make.hh b/source/blender/blenlib/BLI_cpp_type_make.hh
new file mode 100644
index 00000000000..9100b2b9a0f
--- /dev/null
+++ b/source/blender/blenlib/BLI_cpp_type_make.hh
@@ -0,0 +1,250 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#pragma once
+
+/** \file
+ * \ingroup bli
+ */
+
+#include "BLI_cpp_type.hh"
+#include "BLI_utildefines.h"
+
+namespace blender::cpp_type_util {
+
+template<typename T> void default_construct_cb(void *ptr)
+{
+ new (ptr) T;
+}
+template<typename T> void default_construct_indices_cb(void *ptr, IndexMask mask)
+{
+ mask.foreach_index([&](int64_t i) { new (static_cast<T *>(ptr) + i) T; });
+}
+
+template<typename T> void destruct_cb(void *ptr)
+{
+ (static_cast<T *>(ptr))->~T();
+}
+template<typename T> void destruct_indices_cb(void *ptr, IndexMask mask)
+{
+ T *ptr_ = static_cast<T *>(ptr);
+ mask.foreach_index([&](int64_t i) { ptr_[i].~T(); });
+}
+
+template<typename T> void copy_assign_cb(const void *src, void *dst)
+{
+ *static_cast<T *>(dst) = *static_cast<const T *>(src);
+}
+template<typename T> void copy_assign_indices_cb(const void *src, void *dst, IndexMask mask)
+{
+ const T *src_ = static_cast<const T *>(src);
+ T *dst_ = static_cast<T *>(dst);
+
+ mask.foreach_index([&](int64_t i) { dst_[i] = src_[i]; });
+}
+
+template<typename T> void copy_construct_cb(const void *src, void *dst)
+{
+ blender::uninitialized_copy_n(static_cast<const T *>(src), 1, static_cast<T *>(dst));
+}
+template<typename T> void copy_construct_indices_cb(const void *src, void *dst, IndexMask mask)
+{
+ const T *src_ = static_cast<const T *>(src);
+ T *dst_ = static_cast<T *>(dst);
+
+ mask.foreach_index([&](int64_t i) { new (dst_ + i) T(src_[i]); });
+}
+
+template<typename T> void move_assign_cb(void *src, void *dst)
+{
+ blender::initialized_move_n(static_cast<T *>(src), 1, static_cast<T *>(dst));
+}
+template<typename T> void move_assign_indices_cb(void *src, void *dst, IndexMask mask)
+{
+ T *src_ = static_cast<T *>(src);
+ T *dst_ = static_cast<T *>(dst);
+
+ mask.foreach_index([&](int64_t i) { dst_[i] = std::move(src_[i]); });
+}
+
+template<typename T> void move_construct_cb(void *src, void *dst)
+{
+ blender::uninitialized_move_n(static_cast<T *>(src), 1, static_cast<T *>(dst));
+}
+template<typename T> void move_construct_indices_cb(void *src, void *dst, IndexMask mask)
+{
+ T *src_ = static_cast<T *>(src);
+ T *dst_ = static_cast<T *>(dst);
+
+ mask.foreach_index([&](int64_t i) { new (dst_ + i) T(std::move(src_[i])); });
+}
+
+template<typename T> void relocate_assign_cb(void *src, void *dst)
+{
+ T *src_ = static_cast<T *>(src);
+ T *dst_ = static_cast<T *>(dst);
+
+ *dst_ = std::move(*src_);
+ src_->~T();
+}
+template<typename T> void relocate_assign_indices_cb(void *src, void *dst, IndexMask mask)
+{
+ T *src_ = static_cast<T *>(src);
+ T *dst_ = static_cast<T *>(dst);
+
+ mask.foreach_index([&](int64_t i) {
+ dst_[i] = std::move(src_[i]);
+ src_[i].~T();
+ });
+}
+
+template<typename T> void relocate_construct_cb(void *src, void *dst)
+{
+ T *src_ = static_cast<T *>(src);
+ T *dst_ = static_cast<T *>(dst);
+
+ new (dst_) T(std::move(*src_));
+ src_->~T();
+}
+template<typename T> void relocate_construct_indices_cb(void *src, void *dst, IndexMask mask)
+{
+ T *src_ = static_cast<T *>(src);
+ T *dst_ = static_cast<T *>(dst);
+
+ mask.foreach_index([&](int64_t i) {
+ new (dst_ + i) T(std::move(src_[i]));
+ src_[i].~T();
+ });
+}
+
+template<typename T> void fill_assign_cb(const void *value, void *dst, int64_t n)
+{
+ const T &value_ = *static_cast<const T *>(value);
+ T *dst_ = static_cast<T *>(dst);
+
+ for (int64_t i = 0; i < n; i++) {
+ dst_[i] = value_;
+ }
+}
+template<typename T> void fill_assign_indices_cb(const void *value, void *dst, IndexMask mask)
+{
+ const T &value_ = *static_cast<const T *>(value);
+ T *dst_ = static_cast<T *>(dst);
+
+ mask.foreach_index([&](int64_t i) { dst_[i] = value_; });
+}
+
+template<typename T> void fill_construct_cb(const void *value, void *dst, int64_t n)
+{
+ const T &value_ = *static_cast<const T *>(value);
+ T *dst_ = static_cast<T *>(dst);
+
+ for (int64_t i = 0; i < n; i++) {
+ new (dst_ + i) T(value_);
+ }
+}
+template<typename T> void fill_construct_indices_cb(const void *value, void *dst, IndexMask mask)
+{
+ const T &value_ = *static_cast<const T *>(value);
+ T *dst_ = static_cast<T *>(dst);
+
+ mask.foreach_index([&](int64_t i) { new (dst_ + i) T(value_); });
+}
+
+template<typename T> void print_cb(const void *value, std::stringstream &ss)
+{
+ const T &value_ = *static_cast<const T *>(value);
+ ss << value_;
+}
+
+template<typename T> bool is_equal_cb(const void *a, const void *b)
+{
+ const T &a_ = *static_cast<const T *>(a);
+ const T &b_ = *static_cast<const T *>(b);
+ return a_ == b_;
+}
+
+template<typename T> uint64_t hash_cb(const void *value)
+{
+ const T &value_ = *static_cast<const T *>(value);
+ return get_default_hash(value_);
+}
+
+} // namespace blender::cpp_type_util
+
+namespace blender {
+
+template<typename T, CPPTypeFlags Flags>
+CPPType::CPPType(CPPTypeParam<T, Flags> /* unused */, StringRef debug_name)
+{
+ using namespace cpp_type_util;
+
+ debug_name_ = debug_name;
+ size_ = (int64_t)sizeof(T);
+ alignment_ = (int64_t)alignof(T);
+ is_trivial_ = std::is_trivial_v<T>;
+ is_trivially_destructible_ = std::is_trivially_destructible_v<T>;
+ if constexpr (std::is_default_constructible_v<T>) {
+ default_construct_ = default_construct_cb<T>;
+ default_construct_indices_ = default_construct_indices_cb<T>;
+ static T default_value;
+ default_value_ = (void *)&default_value;
+ }
+ if constexpr (std::is_destructible_v<T>) {
+ destruct_ = destruct_cb<T>;
+ destruct_indices_ = destruct_indices_cb<T>;
+ }
+ if constexpr (std::is_copy_assignable_v<T>) {
+ copy_assign_ = copy_assign_cb<T>;
+ copy_assign_indices_ = copy_assign_indices_cb<T>;
+ }
+ if constexpr (std::is_copy_constructible_v<T>) {
+ copy_construct_ = copy_construct_cb<T>;
+ copy_construct_indices_ = copy_construct_indices_cb<T>;
+ }
+ if constexpr (std::is_move_assignable_v<T>) {
+ move_assign_ = move_assign_cb<T>;
+ move_assign_indices_ = move_assign_indices_cb<T>;
+ }
+ if constexpr (std::is_move_constructible_v<T>) {
+ move_construct_ = move_construct_cb<T>;
+ move_construct_indices_ = move_construct_indices_cb<T>;
+ }
+ if constexpr (std::is_destructible_v<T>) {
+ if constexpr (std::is_move_assignable_v<T>) {
+ relocate_assign_ = relocate_assign_cb<T>;
+ relocate_assign_indices_ = relocate_assign_indices_cb<T>;
+ }
+ if constexpr (std::is_move_constructible_v<T>) {
+ relocate_construct_ = relocate_construct_cb<T>;
+ relocate_construct_indices_ = relocate_construct_indices_cb<T>;
+ }
+ }
+ if constexpr (std::is_copy_assignable_v<T>) {
+ fill_assign_indices_ = fill_assign_indices_cb<T>;
+ }
+ if constexpr (std::is_copy_constructible_v<T>) {
+ fill_construct_indices_ = fill_construct_indices_cb<T>;
+ }
+ if constexpr ((bool)(Flags & CPPTypeFlags::Hashable)) {
+ hash_ = hash_cb<T>;
+ }
+ if constexpr ((bool)(Flags & CPPTypeFlags::Printable)) {
+ print_ = print_cb<T>;
+ }
+ if constexpr ((bool)(Flags & CPPTypeFlags::EqualityComparable)) {
+ is_equal_ = is_equal_cb<T>;
+ }
+
+ alignment_mask_ = (uintptr_t)alignment_ - (uintptr_t)1;
+ has_special_member_functions_ = (default_construct_ && copy_construct_ && copy_assign_ &&
+ move_construct_ && move_assign_ && destruct_);
+}
+
+} // namespace blender
+
+#define BLI_CPP_TYPE_MAKE(IDENTIFIER, TYPE_NAME, FLAGS) \
+ template<> const blender::CPPType &blender::CPPType::get_impl<TYPE_NAME>() \
+ { \
+ static CPPType cpp_type{blender::CPPTypeParam<TYPE_NAME, FLAGS>(), STRINGIFY(IDENTIFIER)}; \
+ return cpp_type; \
+ }
diff --git a/source/blender/blenlib/CMakeLists.txt b/source/blender/blenlib/CMakeLists.txt
index 6e3e84f6495..057d94a6f39 100644
--- a/source/blender/blenlib/CMakeLists.txt
+++ b/source/blender/blenlib/CMakeLists.txt
@@ -50,6 +50,7 @@ set(SRC
intern/boxpack_2d.c
intern/buffer.c
intern/convexhull_2d.c
+ intern/cpp_type.cc
intern/delaunay_2d.cc
intern/dot_export.cc
intern/dynlib.c
@@ -170,6 +171,8 @@ set(SRC
BLI_compiler_typecheck.h
BLI_console.h
BLI_convexhull_2d.h
+ BLI_cpp_type_make.hh
+ BLI_cpp_type.hh
BLI_delaunay_2d.h
BLI_dial_2d.h
BLI_disjoint_set.hh
@@ -403,6 +406,7 @@ if(WITH_GTESTS)
tests/BLI_array_utils_test.cc
tests/BLI_bounds_test.cc
tests/BLI_color_test.cc
+ tests/BLI_cpp_type_test.cc
tests/BLI_delaunay_2d_test.cc
tests/BLI_disjoint_set_test.cc
tests/BLI_edgehash_test.cc
diff --git a/source/blender/blenlib/intern/cpp_type.cc b/source/blender/blenlib/intern/cpp_type.cc
new file mode 100644
index 00000000000..72ccc54e552
--- /dev/null
+++ b/source/blender/blenlib/intern/cpp_type.cc
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "BLI_color.hh"
+#include "BLI_cpp_type_make.hh"
+#include "BLI_float4x4.hh"
+#include "BLI_math_vec_types.hh"
+
+BLI_CPP_TYPE_MAKE(bool, bool, CPPTypeFlags::BasicType)
+
+BLI_CPP_TYPE_MAKE(float, float, CPPTypeFlags::BasicType)
+BLI_CPP_TYPE_MAKE(float2, blender::float2, CPPTypeFlags::BasicType)
+BLI_CPP_TYPE_MAKE(float3, blender::float3, CPPTypeFlags::BasicType)
+BLI_CPP_TYPE_MAKE(float4x4, blender::float4x4, CPPTypeFlags::BasicType)
+
+BLI_CPP_TYPE_MAKE(int32, int32_t, CPPTypeFlags::BasicType)
+BLI_CPP_TYPE_MAKE(int8, int8_t, CPPTypeFlags::BasicType)
+BLI_CPP_TYPE_MAKE(uint32, uint32_t, CPPTypeFlags::BasicType)
+BLI_CPP_TYPE_MAKE(uint8, uint8_t, CPPTypeFlags::BasicType)
+
+BLI_CPP_TYPE_MAKE(ColorGeometry4f, blender::ColorGeometry4f, CPPTypeFlags::BasicType)
+BLI_CPP_TYPE_MAKE(ColorGeometry4b, blender::ColorGeometry4b, CPPTypeFlags::BasicType)
+
+BLI_CPP_TYPE_MAKE(string, std::string, CPPTypeFlags::BasicType)
diff --git a/source/blender/blenlib/tests/BLI_cpp_type_test.cc b/source/blender/blenlib/tests/BLI_cpp_type_test.cc
new file mode 100644
index 00000000000..c7e6ed47b24
--- /dev/null
+++ b/source/blender/blenlib/tests/BLI_cpp_type_test.cc
@@ -0,0 +1,326 @@
+/* SPDX-License-Identifier: Apache-2.0 */
+
+#include "testing/testing.h"
+
+#include "BLI_cpp_type.hh"
+#include "BLI_cpp_type_make.hh"
+
+namespace blender::tests {
+
+static const int default_constructed_value = 1;
+static const int copy_constructed_value = 2;
+static const int move_constructed_value = 3;
+static const int copy_constructed_from_value = 4;
+static const int move_constructed_from_value = 5;
+static const int copy_assigned_value = 6;
+static const int copy_assigned_from_value = 7;
+static const int move_assigned_value = 8;
+static const int move_assigned_from_value = 9;
+static const int destructed_value = 10;
+
+struct TestType {
+ mutable volatile int value;
+
+ TestType()
+ {
+ value = default_constructed_value;
+ }
+
+ ~TestType()
+ {
+ value = destructed_value;
+ }
+
+ TestType(const TestType &other)
+ {
+ value = copy_constructed_value;
+ other.value = copy_constructed_from_value;
+ }
+
+ TestType(TestType &&other) noexcept
+ {
+ value = move_constructed_value;
+ other.value = move_constructed_from_value;
+ }
+
+ TestType &operator=(const TestType &other)
+ {
+ value = copy_assigned_value;
+ other.value = copy_assigned_from_value;
+ return *this;
+ }
+
+ TestType &operator=(TestType &&other) noexcept
+ {
+ value = move_assigned_value;
+ other.value = move_assigned_from_value;
+ return *this;
+ }
+
+ friend std::ostream &operator<<(std::ostream &stream, const TestType &value)
+ {
+ stream << value.value;
+ return stream;
+ }
+
+ friend bool operator==(const TestType &UNUSED(a), const TestType &UNUSED(b))
+ {
+ return false;
+ }
+
+ uint64_t hash() const
+ {
+ return 0;
+ }
+};
+
+} // namespace blender::tests
+
+BLI_CPP_TYPE_MAKE(TestType, blender::tests::TestType, CPPTypeFlags::BasicType)
+
+namespace blender::tests {
+
+static const CPPType &CPPType_TestType = CPPType::get<TestType>();
+
+TEST(cpp_type, Size)
+{
+ EXPECT_EQ(CPPType_TestType.size(), sizeof(TestType));
+}
+
+TEST(cpp_type, Alignment)
+{
+ EXPECT_EQ(CPPType_TestType.alignment(), alignof(TestType));
+}
+
+TEST(cpp_type, Is)
+{
+ EXPECT_TRUE(CPPType_TestType.is<TestType>());
+ EXPECT_FALSE(CPPType_TestType.is<int>());
+}
+
+TEST(cpp_type, DefaultConstruction)
+{
+ int buffer[10] = {0};
+ CPPType_TestType.default_construct((void *)buffer);
+ EXPECT_EQ(buffer[0], default_constructed_value);
+ EXPECT_EQ(buffer[1], 0);
+ CPPType_TestType.default_construct_n((void *)buffer, 3);
+ EXPECT_EQ(buffer[0], default_constructed_value);
+ EXPECT_EQ(buffer[1], default_constructed_value);
+ EXPECT_EQ(buffer[2], default_constructed_value);
+ EXPECT_EQ(buffer[3], 0);
+ CPPType_TestType.default_construct_indices((void *)buffer, {2, 5, 7});
+ EXPECT_EQ(buffer[2], default_constructed_value);
+ EXPECT_EQ(buffer[4], 0);
+ EXPECT_EQ(buffer[5], default_constructed_value);
+ EXPECT_EQ(buffer[6], 0);
+ EXPECT_EQ(buffer[7], default_constructed_value);
+ EXPECT_EQ(buffer[8], 0);
+}
+
+TEST(cpp_type, Destruct)
+{
+ int buffer[10] = {0};
+ CPPType_TestType.destruct((void *)buffer);
+ EXPECT_EQ(buffer[0], destructed_value);
+ EXPECT_EQ(buffer[1], 0);
+ CPPType_TestType.destruct_n((void *)buffer, 3);
+ EXPECT_EQ(buffer[0], destructed_value);
+ EXPECT_EQ(buffer[1], destructed_value);
+ EXPECT_EQ(buffer[2], destructed_value);
+ EXPECT_EQ(buffer[3], 0);
+ CPPType_TestType.destruct_indices((void *)buffer, {2, 5, 7});
+ EXPECT_EQ(buffer[2], destructed_value);
+ EXPECT_EQ(buffer[4], 0);
+ EXPECT_EQ(buffer[5], destructed_value);
+ EXPECT_EQ(buffer[6], 0);
+ EXPECT_EQ(buffer[7], destructed_value);
+ EXPECT_EQ(buffer[8], 0);
+}
+
+TEST(cpp_type, CopyToUninitialized)
+{
+ int buffer1[10] = {0};
+ int buffer2[10] = {0};
+ CPPType_TestType.copy_construct((void *)buffer1, (void *)buffer2);
+ EXPECT_EQ(buffer1[0], copy_constructed_from_value);
+ EXPECT_EQ(buffer2[0], copy_constructed_value);
+ CPPType_TestType.copy_construct_n((void *)buffer1, (void *)buffer2, 3);
+ EXPECT_EQ(buffer1[0], copy_constructed_from_value);
+ EXPECT_EQ(buffer2[0], copy_constructed_value);
+ EXPECT_EQ(buffer1[1], copy_constructed_from_value);
+ EXPECT_EQ(buffer2[1], copy_constructed_value);
+ EXPECT_EQ(buffer1[2], copy_constructed_from_value);
+ EXPECT_EQ(buffer2[2], copy_constructed_value);
+ EXPECT_EQ(buffer1[3], 0);
+ EXPECT_EQ(buffer2[3], 0);
+ CPPType_TestType.copy_construct_indices((void *)buffer1, (void *)buffer2, {2, 5, 7});
+ EXPECT_EQ(buffer1[2], copy_constructed_from_value);
+ EXPECT_EQ(buffer2[2], copy_constructed_value);
+ EXPECT_EQ(buffer1[4], 0);
+ EXPECT_EQ(buffer2[4], 0);
+ EXPECT_EQ(buffer1[5], copy_constructed_from_value);
+ EXPECT_EQ(buffer2[5], copy_constructed_value);
+ EXPECT_EQ(buffer1[6], 0);
+ EXPECT_EQ(buffer2[6], 0);
+ EXPECT_EQ(buffer1[7], copy_constructed_from_value);
+ EXPECT_EQ(buffer2[7], copy_constructed_value);
+ EXPECT_EQ(buffer1[8], 0);
+ EXPECT_EQ(buffer2[8], 0);
+}
+
+TEST(cpp_type, CopyToInitialized)
+{
+ int buffer1[10] = {0};
+ int buffer2[10] = {0};
+ CPPType_TestType.copy_assign((void *)buffer1, (void *)buffer2);
+ EXPECT_EQ(buffer1[0], copy_assigned_from_value);
+ EXPECT_EQ(buffer2[0], copy_assigned_value);
+ CPPType_TestType.copy_assign_n((void *)buffer1, (void *)buffer2, 3);
+ EXPECT_EQ(buffer1[0], copy_assigned_from_value);
+ EXPECT_EQ(buffer2[0], copy_assigned_value);
+ EXPECT_EQ(buffer1[1], copy_assigned_from_value);
+ EXPECT_EQ(buffer2[1], copy_assigned_value);
+ EXPECT_EQ(buffer1[2], copy_assigned_from_value);
+ EXPECT_EQ(buffer2[2], copy_assigned_value);
+ EXPECT_EQ(buffer1[3], 0);
+ EXPECT_EQ(buffer2[3], 0);
+ CPPType_TestType.copy_assign_indices((void *)buffer1, (void *)buffer2, {2, 5, 7});
+ EXPECT_EQ(buffer1[2], copy_assigned_from_value);
+ EXPECT_EQ(buffer2[2], copy_assigned_value);
+ EXPECT_EQ(buffer1[4], 0);
+ EXPECT_EQ(buffer2[4], 0);
+ EXPECT_EQ(buffer1[5], copy_assigned_from_value);
+ EXPECT_EQ(buffer2[5], copy_assigned_value);
+ EXPECT_EQ(buffer1[6], 0);
+ EXPECT_EQ(buffer2[6], 0);
+ EXPECT_EQ(buffer1[7], copy_assigned_from_value);
+ EXPECT_EQ(buffer2[7], copy_assigned_value);
+ EXPECT_EQ(buffer1[8], 0);
+ EXPECT_EQ(buffer2[8], 0);
+}
+
+TEST(cpp_type, RelocateToUninitialized)
+{
+ int buffer1[10] = {0};
+ int buffer2[10] = {0};
+ CPPType_TestType.relocate_construct((void *)buffer1, (void *)buffer2);
+ EXPECT_EQ(buffer1[0], destructed_value);
+ EXPECT_EQ(buffer2[0], move_constructed_value);
+ CPPType_TestType.relocate_construct_n((void *)buffer1, (void *)buffer2, 3);
+ EXPECT_EQ(buffer1[0], destructed_value);
+ EXPECT_EQ(buffer2[0], move_constructed_value);
+ EXPECT_EQ(buffer1[1], destructed_value);
+ EXPECT_EQ(buffer2[1], move_constructed_value);
+ EXPECT_EQ(buffer1[2], destructed_value);
+ EXPECT_EQ(buffer2[2], move_constructed_value);
+ EXPECT_EQ(buffer1[3], 0);
+ EXPECT_EQ(buffer2[3], 0);
+ CPPType_TestType.relocate_construct_indices((void *)buffer1, (void *)buffer2, {2, 5, 7});
+ EXPECT_EQ(buffer1[2], destructed_value);
+ EXPECT_EQ(buffer2[2], move_constructed_value);
+ EXPECT_EQ(buffer1[4], 0);
+ EXPECT_EQ(buffer2[4], 0);
+ EXPECT_EQ(buffer1[5], destructed_value);
+ EXPECT_EQ(buffer2[5], move_constructed_value);
+ EXPECT_EQ(buffer1[6], 0);
+ EXPECT_EQ(buffer2[6], 0);
+ EXPECT_EQ(buffer1[7], destructed_value);
+ EXPECT_EQ(buffer2[7], move_constructed_value);
+ EXPECT_EQ(buffer1[8], 0);
+ EXPECT_EQ(buffer2[8], 0);
+}
+
+TEST(cpp_type, RelocateToInitialized)
+{
+ int buffer1[10] = {0};
+ int buffer2[10] = {0};
+ CPPType_TestType.relocate_assign((void *)buffer1, (void *)buffer2);
+ EXPECT_EQ(buffer1[0], destructed_value);
+ EXPECT_EQ(buffer2[0], move_assigned_value);
+ CPPType_TestType.relocate_assign_n((void *)buffer1, (void *)buffer2, 3);
+ EXPECT_EQ(buffer1[0], destructed_value);
+ EXPECT_EQ(buffer2[0], move_assigned_value);
+ EXPECT_EQ(buffer1[1], destructed_value);
+ EXPECT_EQ(buffer2[1], move_assigned_value);
+ EXPECT_EQ(buffer1[2], destructed_value);
+ EXPECT_EQ(buffer2[2], move_assigned_value);
+ EXPECT_EQ(buffer1[3], 0);
+ EXPECT_EQ(buffer2[3], 0);
+ CPPType_TestType.relocate_assign_indices((void *)buffer1, (void *)buffer2, {2, 5, 7});
+ EXPECT_EQ(buffer1[2], destructed_value);
+ EXPECT_EQ(buffer2[2], move_assigned_value);
+ EXPECT_EQ(buffer1[4], 0);
+ EXPECT_EQ(buffer2[4], 0);
+ EXPECT_EQ(buffer1[5], destructed_value);
+ EXPECT_EQ(buffer2[5], move_assigned_value);
+ EXPECT_EQ(buffer1[6], 0);
+ EXPECT_EQ(buffer2[6], 0);
+ EXPECT_EQ(buffer1[7], destructed_value);
+ EXPECT_EQ(buffer2[7], move_assigned_value);
+ EXPECT_EQ(buffer1[8], 0);
+ EXPECT_EQ(buffer2[8], 0);
+}
+
+TEST(cpp_type, FillInitialized)
+{
+ int buffer1 = 0;
+ int buffer2[10] = {0};
+ CPPType_TestType.fill_assign_n((void *)&buffer1, (void *)buffer2, 3);
+ EXPECT_EQ(buffer1, copy_assigned_from_value);
+ EXPECT_EQ(buffer2[0], copy_assigned_value);
+ EXPECT_EQ(buffer2[1], copy_assigned_value);
+ EXPECT_EQ(buffer2[2], copy_assigned_value);
+ EXPECT_EQ(buffer2[3], 0);
+
+ buffer1 = 0;
+ CPPType_TestType.fill_assign_indices((void *)&buffer1, (void *)buffer2, {1, 6, 8});
+ EXPECT_EQ(buffer1, copy_assigned_from_value);
+ EXPECT_EQ(buffer2[0], copy_assigned_value);
+ EXPECT_EQ(buffer2[1], copy_assigned_value);
+ EXPECT_EQ(buffer2[2], copy_assigned_value);
+ EXPECT_EQ(buffer2[3], 0);
+ EXPECT_EQ(buffer2[4], 0);
+ EXPECT_EQ(buffer2[5], 0);
+ EXPECT_EQ(buffer2[6], copy_assigned_value);
+ EXPECT_EQ(buffer2[7], 0);
+ EXPECT_EQ(buffer2[8], copy_assigned_value);
+ EXPECT_EQ(buffer2[9], 0);
+}
+
+TEST(cpp_type, FillUninitialized)
+{
+ int buffer1 = 0;
+ int buffer2[10] = {0};
+ CPPType_TestType.fill_construct_n((void *)&buffer1, (void *)buffer2, 3);
+ EXPECT_EQ(buffer1, copy_constructed_from_value);
+ EXPECT_EQ(buffer2[0], copy_constructed_value);
+ EXPECT_EQ(buffer2[1], copy_constructed_value);
+ EXPECT_EQ(buffer2[2], copy_constructed_value);
+ EXPECT_EQ(buffer2[3], 0);
+
+ buffer1 = 0;
+ CPPType_TestType.fill_construct_indices((void *)&buffer1, (void *)buffer2, {1, 6, 8});
+ EXPECT_EQ(buffer1, copy_constructed_from_value);
+ EXPECT_EQ(buffer2[0], copy_constructed_value);
+ EXPECT_EQ(buffer2[1], copy_constructed_value);
+ EXPECT_EQ(buffer2[2], copy_constructed_value);
+ EXPECT_EQ(buffer2[3], 0);
+ EXPECT_EQ(buffer2[4], 0);
+ EXPECT_EQ(buffer2[5], 0);
+ EXPECT_EQ(buffer2[6], copy_constructed_value);
+ EXPECT_EQ(buffer2[7], 0);
+ EXPECT_EQ(buffer2[8], copy_constructed_value);
+ EXPECT_EQ(buffer2[9], 0);
+}
+
+TEST(cpp_type, DebugPrint)
+{
+ int value = 42;
+ std::stringstream ss;
+ CPPType::get<int32_t>().print((void *)&value, ss);
+ std::string text = ss.str();
+ EXPECT_EQ(text, "42");
+}
+
+} // namespace blender::tests