Changeset View
Changeset View
Standalone View
Standalone View
source/blender/blenlib/BLI_cpp_type.hh
| Show All 16 Lines | |||||
| * - If the data type is known to be one of a few, it depends on how performance sensitive the code | * - If the data type is known to be one of a few, it depends on how performance sensitive the code | ||||
| * is. | * is. | ||||
| * - If it it's a small hot loop, a template can be used to optimize for every type (at the | * - 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 | * cost of longer compile time, a larger binary and the complexity that comes from using | ||||
| * templates). | * templates). | ||||
| * - If the code is not performance sensitive, it usually makes sense to use #CPPType instead. | * - 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 | * - 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. | * some types, while there is a fallback code path using #CPPType for all other types. | ||||
| * #CPPType::to_static_type allows dispatching between both versions based on the type. | |||||
| * | * | ||||
| * Under some circumstances, #CPPType serves a similar role as #std::type_info. However, #CPPType | * 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 | * has much more utility because it contains methods for actually working with instances of the | ||||
| * type. | * 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. | ||||
| Show All 33 Lines | |||||
| * 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. | ||||
| */ | */ | ||||
| #include "BLI_hash.hh" | #include "BLI_hash.hh" | ||||
| #include "BLI_index_mask.hh" | #include "BLI_index_mask.hh" | ||||
| #include "BLI_map.hh" | |||||
| #include "BLI_math_base.h" | #include "BLI_math_base.h" | ||||
| #include "BLI_string_ref.hh" | #include "BLI_string_ref.hh" | ||||
| #include "BLI_utility_mixins.hh" | #include "BLI_utility_mixins.hh" | ||||
| /** | /** | ||||
| * Different types support different features. Features like copy constructability can be detected | * 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 | * 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 and need to be determined by the programmer. | ||||
| ▲ Show 20 Lines • Show All 556 Lines • ▼ Show 20 Lines | public: | ||||
| { | { | ||||
| return destruct_; | return destruct_; | ||||
| } | } | ||||
| 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>>(); | ||||
| } | } | ||||
| /** | |||||
| * Convert a #CPPType that is only known at run-time, to a static type that is known at | |||||
| * compile-time. This allows the compiler to optimize a function for specific types, while all | |||||
| * other types can still use a generic fallback function. | |||||
| * | |||||
| * \param Types The types that code should be generated for. | |||||
| * \param fn The function object to call. This is expected to have a templated `operator()` and a | |||||
| * non-templated `operator()`. The templated version will be called if the current #CPPType | |||||
| * matches any of the given types. Otherwise, the non-templated function is called. | |||||
| */ | |||||
| template<typename... Types, typename Fn> void to_static_type(const Fn &fn) const | |||||
| { | |||||
| using Callback = void (*)(const Fn &fn); | |||||
| /* Build a lookup table to avoid having to compare the current #CPPType with every type in | |||||
| * #Types one after another. */ | |||||
| static const Map<const CPPType *, Callback> callback_map = []() { | |||||
| Map<const CPPType *, Callback> callback_map; | |||||
| /* This adds an entry in the map for every type in #Types. */ | |||||
| (callback_map.add_new(&CPPType::get<Types>(), | |||||
| [](const Fn &fn) { | |||||
| /* Call the templated `operator()` of the given function object. */ | |||||
| fn.template operator()<Types>(); | |||||
| }), | |||||
| ...); | |||||
| return callback_map; | |||||
| }(); | |||||
| const Callback callback = callback_map.lookup_default(this, nullptr); | |||||
| if (callback != nullptr) { | |||||
| callback(fn); | |||||
| } | |||||
| else { | |||||
| /* Call the non-templated `operator()` of the given function object. */ | |||||
| fn(); | |||||
| } | |||||
| } | |||||
| template<typename T> struct type_tag { | |||||
| using type = T; | |||||
| }; | |||||
| private: | |||||
| template<typename Fn> struct TypeTagExecutor { | |||||
| const Fn &fn; | |||||
| template<typename T> void operator()() const | |||||
| { | |||||
| fn(type_tag<T>{}); | |||||
| } | |||||
| void operator()() const | |||||
| { | |||||
| fn(type_tag<void>{}); | |||||
| } | |||||
| }; | |||||
| public: | |||||
| /** | |||||
| * Similar to #to_static_type but is easier to use with a lambda function. The function is | |||||
| * expected to take a single `auto type_tag` parameter. To extract the static type, use: | |||||
| * `using T = typename decltype(type_tag)::type;` | |||||
| * | |||||
| * If the current #CPPType is not in #Types, the type tag is `void`. | |||||
| */ | |||||
| template<typename... Types, typename Fn> void to_static_type_tag(const Fn &fn) const | |||||
| { | |||||
| TypeTagExecutor<Fn> executor{fn}; | |||||
| this->to_static_type<Types...>(executor); | |||||
| } | |||||
| }; | }; | ||||
| } // namespace blender | } // 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(); | ||||