Changeset View
Changeset View
Standalone View
Standalone View
source/blender/blenlib/BLI_cpp_type.hh
- This file was moved from source/blender/functions/FN_cpp_type.hh.
| /* SPDX-License-Identifier: GPL-2.0-or-later */ | /* SPDX-License-Identifier: GPL-2.0-or-later */ | ||||
| #pragma once | #pragma once | ||||
| /** \file | /** \file | ||||
| * \ingroup fn | * \ingroup bli | ||||
| * | * | ||||
| * The `CPPType` class is the core of a runtime-type-system. It allows working with arbitrary C++ | * The `CPPType` class allows working with arbitrary C++ types in a generic way. An instance of | ||||
| * types in a generic way. An instance of `CPPType` wraps exactly one type like `int` or | * #CPPType wraps exactly one type like `int` or `std::string`. | ||||
| * `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, | * 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 | * has to make sure that alignment rules are followed. The methods provided by a #CPPType instance | ||||
| * will check for correct alignment as well. | * 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. | * 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 | * 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. | * 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 | * A #CPPType instance comes with many methods that allow dealing with types in a generic way. Most | ||||
| * methods come in three variants. Using the construct-default methods as example: | * methods come in three variants. Using the default-construct methods as example: | ||||
HooglyBoogly: `as example` -> `as an example` | |||||
| * - default_construct(void *ptr): | * - `default_construct(void *ptr)`: | ||||
| * Constructs a single instance of that type at the given pointer. | * Constructs a single instance of that type at the given pointer. | ||||
| * - default_construct_n(void *ptr, int64_t n): | * - `default_construct_n(void *ptr, int64_t n)`: | ||||
| * Constructs n instances of that type in an array that starts at the given pointer. | * Constructs n instances of that type in an array that starts at the given pointer. | ||||
| * - default_construct_indices(void *ptr, IndexMask mask): | * - `default_construct_indices(void *ptr, IndexMask mask)`: | ||||
| * Constructs multiple instances of that type in an array that starts at the given pointer. | * Constructs multiple instances of that type in an array that starts at the given pointer. | ||||
| * Only the indices referenced by `mask` will by constructed. | * Only the indices referenced by `mask` will by constructed. | ||||
| * | * | ||||
| * In some cases default-construction does nothing (e.g. for trivial types like int). The | * 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` 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. | * default value is, depends on the type. Usually it is something like 0 or an empty string. | ||||
| * | * | ||||
| * | * | ||||
| * Implementation Considerations | * Implementation Considerations | ||||
| * ----------------------------- | * ----------------------------- | ||||
| * | * | ||||
| * Concepts like inheritance are currently not captured by this system. This is not because it is | * 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. | * not possible, but because it was not necessary to add this complexity yet. | ||||
| * | * | ||||
| * One could also implement CPPType itself using virtual inheritance. However, I found the approach | * One could also implement CPPType itself using virtual methods and a child class for every | ||||
| * used now with explicit function pointers to work better. Here are some reasons: | * 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 | * - 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. | * that would only be instanced once each. | ||||
| * - Methods like `default_construct` that operate on a single instance have to be fast. Even this | * - 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 | * 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. | * 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 | * - If it becomes necessary, we could pass the function pointers to C functions more easily than | ||||
| * pointers to virtual member functions. | * pointers to virtual member functions. | ||||
| */ | */ | ||||
| Show All 14 Lines | enum class CPPTypeFlags { | ||||
| Hashable = 1 << 0, | Hashable = 1 << 0, | ||||
| Printable = 1 << 1, | Printable = 1 << 1, | ||||
| EqualityComparable = 1 << 2, | EqualityComparable = 1 << 2, | ||||
| BasicType = Hashable | Printable | EqualityComparable, | BasicType = Hashable | Printable | EqualityComparable, | ||||
| }; | }; | ||||
| ENUM_OPERATORS(CPPTypeFlags, CPPTypeFlags::EqualityComparable) | ENUM_OPERATORS(CPPTypeFlags, CPPTypeFlags::EqualityComparable) | ||||
| namespace blender::fn { | namespace blender { | ||||
| /** Utility class to pass template parameters to constructor of `CPPType`. */ | /** Utility class to pass template parameters to constructor of `CPPType`. */ | ||||
| template<typename T, CPPTypeFlags Flags> struct CPPTypeParam { | template<typename T, CPPTypeFlags Flags> struct CPPTypeParam { | ||||
| }; | }; | ||||
| class CPPType : NonCopyable, NonMovable { | class CPPType : NonCopyable, NonMovable { | ||||
| private: | private: | ||||
| int64_t size_ = 0; | int64_t size_ = 0; | ||||
| ▲ Show 20 Lines • Show All 538 Lines • ▼ Show 20 Lines | public: | ||||
| } | } | ||||
| template<typename T> bool is() const | template<typename T> bool is() const | ||||
| { | { | ||||
| return this == &CPPType::get<std::decay_t<T>>(); | return this == &CPPType::get<std::decay_t<T>>(); | ||||
| } | } | ||||
| }; | }; | ||||
| } // namespace blender::fn | } // namespace blender | ||||
| /* Utility for allocating an uninitialized buffer for a single value of the given #CPPType. */ | /* Utility for allocating an uninitialized buffer for a single value of the given #CPPType. */ | ||||
| #define BUFFER_FOR_CPP_TYPE_VALUE(type, variable_name) \ | #define BUFFER_FOR_CPP_TYPE_VALUE(type, variable_name) \ | ||||
| blender::DynamicStackBuffer<64, 64> stack_buffer_for_##variable_name((type).size(), \ | blender::DynamicStackBuffer<64, 64> stack_buffer_for_##variable_name((type).size(), \ | ||||
| (type).alignment()); \ | (type).alignment()); \ | ||||
| void *variable_name = stack_buffer_for_##variable_name.buffer(); | void *variable_name = stack_buffer_for_##variable_name.buffer(); | ||||
as example -> as an example