/* SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once /** \file * \ingroup bli * * A #blender::Any is a type-safe container for single values of any copy constructible type. * It is similar to #std::any but provides the following two additional features: * - Adjustable inline buffer capacity and alignment. #std::any has a small inline buffer in most * implementations as well, but its size is not guaranteed. * - Can store additional user-defined type information without increasing the stack size of #Any. */ #include #include #include #include "BLI_memory_utils.hh" namespace blender { namespace detail { /** * Contains function pointers that manage the memory in an #Any. * Additional type specific #ExtraInfo can be embedded here as well. */ template struct AnyTypeInfo { /* The pointers are allowed to be null, which means that the implementation is trivial. */ void (*copy_construct)(void *dst, const void *src); void (*move_construct)(void *dst, void *src); void (*destruct)(void *src); const void *(*get)(const void *src); ExtraInfo extra_info; }; /** * Used when #T is stored directly in the inline buffer of the #Any. */ template inline constexpr AnyTypeInfo info_for_inline = { is_trivially_copy_constructible_extended_v ? nullptr : +[](void *dst, const void *src) { new (dst) T(*static_cast(src)); }, is_trivially_move_constructible_extended_v ? nullptr : +[](void *dst, void *src) { new (dst) T(std::move(*static_cast(src))); }, is_trivially_destructible_extended_v ? nullptr : +[](void *src) { std::destroy_at((static_cast(src))); }, nullptr, ExtraInfo::template get()}; /** * Used when #T can't be stored directly in the inline buffer and is stored in a #std::unique_ptr * instead. In this scenario, the #std::unique_ptr is stored in the inline buffer. */ template using Ptr = std::unique_ptr; template inline constexpr AnyTypeInfo info_for_unique_ptr = { [](void *dst, const void *src) { new (dst) Ptr(new T(**(const Ptr *)src)); }, [](void *dst, void *src) { new (dst) Ptr(new T(std::move(**(Ptr *)src))); }, [](void *src) { std::destroy_at((Ptr *)src); }, [](const void *src) -> const void * { return &**(const Ptr *)src; }, ExtraInfo::template get()}; /** * Dummy extra info that is used when no additional type information should be stored in the #Any. */ struct NoExtraInfo { template static constexpr NoExtraInfo get() { return {}; } }; } // namespace detail template< /** * Either void or a struct that contains data members for additional type information. * The struct has to have a static `ExtraInfo get()` method that initializes the struct * based on a type. */ typename ExtraInfo = void, /** * Size of the inline buffer. This allows types that are small enough to be stored directly * inside the #Any without an additional allocation. */ size_t InlineBufferCapacity = 8, /** * Required minimum alignment of the inline buffer. If this is smaller than the alignment * requirement of a used type, a separate allocation is necessary. */ size_t Alignment = 8> class Any { private: /* Makes it possible to use void in the template parameters. */ using RealExtraInfo = std::conditional_t, detail::NoExtraInfo, ExtraInfo>; using Info = detail::AnyTypeInfo; static constexpr size_t RealInlineBufferCapacity = std::max(InlineBufferCapacity, sizeof(std::unique_ptr)); /** * Inline buffer that either contains nothing, the stored value directly, or a #std::unique_ptr * to the value. */ AlignedBuffer buffer_{}; /** * Information about the type that is currently stored. * This is null when the #Any does not contain a value. */ const Info *info_ = nullptr; public: /** Only copy constructible types can be stored in #Any. */ template static constexpr inline bool is_allowed_v = std::is_copy_constructible_v; /** * Checks if the type will be stored in the inline buffer or if it requires a separate * allocation. */ template static constexpr inline bool is_inline_v = std::is_nothrow_move_constructible_v && sizeof(T) <= InlineBufferCapacity && alignof(T) <= Alignment; /** * Checks if #T is the same type as this #Any, because in this case the behavior of e.g. the * assignment operator is different. */ template static constexpr inline bool is_same_any_v = std::is_same_v, Any>; private: template const Info &get_info() const { using DecayT = std::decay_t; static_assert(is_allowed_v); if constexpr (is_inline_v) { return detail::template info_for_inline; } else { return detail::template info_for_unique_ptr; } } public: Any() = default; Any(const Any &other) : info_(other.info_) { if (info_ != nullptr) { if (info_->copy_construct != nullptr) { info_->copy_construct(&buffer_, &other.buffer_); } else { memcpy(&buffer_, &other.buffer_, RealInlineBufferCapacity); } } } /** * \note The #other #Any will not be empty afterwards if it was not before. Just its value is in * a moved-from state. */ Any(Any &&other) noexcept : info_(other.info_) { if (info_ != nullptr) { if (info_->move_construct != nullptr) { info_->move_construct(&buffer_, &other.buffer_); } else { memcpy(&buffer_, &other.buffer_, RealInlineBufferCapacity); } } } /** * Constructs a new #Any that contains the given type #T from #args. The #std::in_place_type_t is * used to disambiguate this and the copy/move constructors. */ template explicit Any(std::in_place_type_t, Args &&...args) { this->emplace_on_empty(std::forward(args)...); } /** * Constructs a new #Any that contains the given value. */ template))> Any(T &&value) : Any(std::in_place_type, std::forward(value)) { } ~Any() { if (info_ != nullptr) { if (info_->destruct != nullptr) { info_->destruct(&buffer_); } } } /** * \note Only needed because the template below does not count as copy assignment operator. */ Any &operator=(const Any &other) { if (this == &other) { return *this; } this->~Any(); new (this) Any(other); return *this; } /** Assign any value to the #Any. */ template Any &operator=(T &&other) { if constexpr (is_same_any_v) { if (this == &other) { return *this; } } this->~Any(); new (this) Any(std::forward(other)); return *this; } /** Destruct any existing value to make it empty. */ void reset() { if (info_ != nullptr) { if (info_->destruct != nullptr) { info_->destruct(&buffer_); } } info_ = nullptr; } operator bool() const { return this->has_value(); } bool has_value() const { return info_ != nullptr; } template std::decay_t &emplace(Args &&...args) { this->~Any(); new (this) Any(std::in_place_type, std::forward(args)...); return this->get(); } template std::decay_t &emplace_on_empty(Args &&...args) { BLI_assert(!this->has_value()); using DecayT = std::decay_t; static_assert(is_allowed_v); info_ = &this->template get_info(); if constexpr (is_inline_v) { /* Construct the value directly in the inline buffer. */ DecayT *stored_value = new (&buffer_) DecayT(std::forward(args)...); return *stored_value; } else { /* Construct the value in a new allocation and store a #std::unique_ptr to it in the inline * buffer. */ std::unique_ptr *stored_value = new (&buffer_) std::unique_ptr(new DecayT(std::forward(args)...)); return **stored_value; } } /** Return true when the value that is currently stored is a #T. */ template bool is() const { return info_ == &this->template get_info(); } /** Get a pointer to the stored value. */ void *get() { BLI_assert(info_ != nullptr); if (info_->get != nullptr) { return const_cast(info_->get(&buffer_)); } return &buffer_; } /** Get a pointer to the stored value. */ const void *get() const { BLI_assert(info_ != nullptr); if (info_->get != nullptr) { return info_->get(&buffer_); } return &buffer_; } /** * Get a reference to the stored value. This invokes undefined behavior when #T does not have the * correct type. */ template std::decay_t &get() { BLI_assert(this->is()); return *static_cast *>(this->get()); } /** * Get a reference to the stored value. This invokes undefined behavior when #T does not have the * correct type. */ template const std::decay_t &get() const { BLI_assert(this->is()); return *static_cast *>(this->get()); } /** * Get extra information that has been stored for the contained type. */ const RealExtraInfo &extra_info() const { BLI_assert(info_ != nullptr); return info_->extra_info; } }; } // namespace blender