diff options
author | Jacques Lucke <jacques@blender.org> | 2022-03-18 12:57:45 +0300 |
---|---|---|
committer | Jacques Lucke <jacques@blender.org> | 2022-03-18 12:57:45 +0300 |
commit | 2252bc6a5527cd7360d1ccfe7a2d1bc640a8dfa6 (patch) | |
tree | bb6f8921b9977dc2c387b750a29aa6ded06ec5f9 /source/blender/blenlib/BLI_cpp_type.hh | |
parent | 7509a741166c97f5f87ed9e580eef6f99eb1e62a (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/BLI_cpp_type.hh')
-rw-r--r-- | source/blender/blenlib/BLI_cpp_type.hh | 654 |
1 files changed, 654 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(); |