Last active
November 21, 2025 21:40
-
-
Save ericniebler/ab42506b8674f1f9633a004427e8a79c to your computer and use it in GitHub Desktop.
another take on a type-erasure library
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| * Copyright (c) 2025 NVIDIA Corporation | |
| * | |
| * Licensed under the Apache License Version 2.0 with LLVM Exceptions | |
| * (the "License"); you may not use this file except in compliance with | |
| * the License. You may obtain a copy of the License at | |
| * | |
| * https://llvm.org/LICENSE.txt | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| #include <cassert> | |
| #include <cstdarg> | |
| #include <cstddef> | |
| #include <cstdint> | |
| #include <cstdio> | |
| #include <concepts> | |
| #include <exception> | |
| #include <memory> | |
| #include <new> | |
| #include <span> | |
| #include <type_traits> | |
| #include <utility> | |
| #if __cpp_rtti || _MSC_VER // MSVC has the typeid operator even with RTTI off | |
| #include <typeinfo> | |
| #define HAS_TYPEID 1 | |
| #else | |
| #define HAS_TYPEID 0 | |
| #endif | |
| #if defined(__clang__) | |
| #define ALWAYS_INLINE gnu::always_inline, gnu::artificial, gnu::nodebug | |
| #elif defined(__GNUC__) | |
| #define ALWAYS_INLINE gnu::always_inline, gnu::artificial | |
| #else | |
| #define ALWAYS_INLINE | |
| #endif | |
| #if defined(_MSC_VER) | |
| #define EMPTY_BASES msvc::empty_bases | |
| #define NO_UNIQUE_ADDRESS msvc::no_unique_address | |
| #else | |
| #define EMPTY_BASES | |
| #define NO_UNIQUE_ADDRESS no_unique_address | |
| #endif | |
| #define ASSERT(...) ((__VA_ARGS__) ? void() : assert(__VA_ARGS__)) | |
| ////////////////////////////////////////////////////////////////////////////////////////// | |
| //! any: a library for ad hoc polymorphism with value semantics | |
| //! | |
| //! @par Terminology: | |
| //! | |
| //! - "root": | |
| //! | |
| //! A type satisfying the @c root concept that is used as the nucleus of a "model". | |
| //! There are 5 root types: | |
| //! | |
| //! - @c _iroot: the abstract root | |
| //! - @c _value_root: holds a concrete value | |
| //! - @c _reference_root: holds a concrete reference | |
| //! - @c _value_proxy_root: holds a type-erased value model | |
| //! - @c _reference_proxy_root: holds a type-erased reference model | |
| //! | |
| //! Aside from @c _iroot, all root types inherit from @c iabstract<Interface>, where | |
| //! @c Interface is the interface that the root type implements. | |
| //! | |
| //! The @c root concept is defined as: | |
| //! | |
| //! @code | |
| //! template <class Root> | |
| //! concept root = requires (Root& root) | |
| //! { | |
| //! any::value(root); | |
| //! { any::reset(root); } -> std::same_as<void>; | |
| //! { any::type(root) } -> std::same_as<const type_info &>; | |
| //! { any::data(root) } -> std::same_as<void *>; | |
| //! { any::empty(root) } -> std::same_as<bool>; | |
| //! }; | |
| //! @endcode | |
| //! | |
| //! - "model": | |
| //! | |
| //! A polymorphic wrapper around a root that is constructed by recursively applying a | |
| //! given interface and its base interfaces to the root type. For example, given an | |
| //! interface @c Derived that extends @c Base, the value proxy model is a type derived | |
| //! from @c Derived<Base<_value_proxy_root<Derived>>>. Model types implement their given | |
| //! interfaces in terms of the root type. There are 5 model types: | |
| //! | |
| //! - @c iabstract: akin to an abstract base class for the | |
| //! interface | |
| //! - @c _value_model: implements the interface for a concrete value | |
| //! - @c _reference_model: implements the interface for a concrete | |
| //! reference | |
| //! - @c _value_proxy_model: implements the interface over a type-erased | |
| //! value model | |
| //! - @c _reference_proxy_model: implements the interface over a type-erased | |
| //! reference model | |
| //! | |
| //! - "proxy": | |
| //! | |
| //! A level of indirection that stores either a type-erased model in a small buffer or a | |
| //! pointer to an object stored elsewhere. The @c _value_proxy_root and @c | |
| //! _reference_proxy_root types model the @c root concept and contain an array of bytes | |
| //! in which they stores either a polymorphic model in-situ or a (tagged) pointer to a | |
| //! heap-allocated model. The @c _value_proxy_model and @c _reference_proxy_model types | |
| //! implement the given interface in terms of the root type. | |
| //! | |
| //! @par Notes: | |
| //! | |
| //! - @c Interface<Base> inherits directly from @c | |
| //! any::interface<Interface,Base>, which | |
| //! inherits directly from @c Base. | |
| //! | |
| //! - Given an interface template @c Derived that extends @c Base, the type @c | |
| //! iabstract<Derived> is derived from @c iabstract<Base>. | |
| //! | |
| //! - In the case of multiple interface extension, the inheritance is forced to be linear. | |
| //! As a result, for an interface @c C that extends @c A and @c B (in that order), @c | |
| //! iabstract<C> will have a linear inheritance hierarchy; it will be an alias for @c | |
| //! C<B<A<_iroot>>>. The result is that @c iabstract<C> inherits from @c iabstract<A> | |
| //! but not from @c iabstract<B>. | |
| //! | |
| //! - The "`_proxy_root`" types both implement an @c emplace function that accepts a | |
| //! concrete value or reference, wraps it in the appropriate "`_model`" type, and stores | |
| //! it either in-situ or on the heap depending on its size and whether it is nothrow | |
| //! moveable. | |
| //! | |
| //! - The @c _root types (excluding @c _iroot) all inherit from @c iabstract<Interface>. | |
| //! The @c _model types implement the interface in terms of the root type. | |
| //! | |
| //! - @c any<Derived> inherits from @c _value_proxy_model<Derived>, which in turn inherits | |
| //! from @c Derived<Base<_value_proxy_root<Derived>>>, which in turn inherits from | |
| //! @c Derived<Base<_iroot>> (aka @c iabstract<Derived> ). | |
| //! | |
| //! - @c any_const_ptr<Derived> inherits from @c _reference_proxy_model<Derived>, which in | |
| //! turn inherits from @c Derived<Base<_reference_proxy_root<Derived>>>. | |
| //! | |
| //! - For every @c any<Interface> instantiation, there are 5 instantiations of | |
| //! @c Interface: | |
| //! | |
| //! 1. @c Interface<...Bases...<_iroot>>>...> | |
| //! 2. @c Interface<...Bases...<_value_root<Value,Interface>>...> | |
| //! 3. @c Interface<...Bases...<_reference_root<Value,Interface>>...> | |
| //! 4. @c Interface<...Bases...<_value_proxy_root<Interface>>...> | |
| //! 5. @c Interface<...Bases...<_reference_proxy_root<Interface>>...> | |
| namespace any | |
| { | |
| ////////////////////////////////////////////////////////////////////////////////////////// | |
| // start_lifetime_as | |
| #if __cpp_lib_start_lifetime_as | |
| using std::start_lifetime_as; | |
| #else | |
| template <class T> | |
| [[ALWAYS_INLINE, nodiscard]] | |
| inline T *start_lifetime_as(void *p) noexcept | |
| { | |
| return std::launder(static_cast<T *>(p)); | |
| } | |
| template <class T> | |
| [[ALWAYS_INLINE, nodiscard]] | |
| inline T const *start_lifetime_as(const void *p) noexcept | |
| { | |
| return std::launder(static_cast<T const *>(p)); | |
| } | |
| #endif | |
| #if __cpp_lib_unreachable | |
| using std::unreachable; | |
| #else | |
| [[noreturn]] inline void unreachable() | |
| { | |
| // Uses compiler specific extensions if possible. | |
| // Even if no extension is used, undefined behavior is still raised by | |
| // an empty function body and the noreturn attribute. | |
| #if defined(_MSC_VER) && !defined(__clang__) // MSVC | |
| __assume(false); | |
| #else // GCC, Clang | |
| __builtin_unreachable(); | |
| #endif | |
| } | |
| #endif | |
| ////////////////////////////////////////////////////////////////////////////////////////// | |
| // type_info and TYPEID | |
| #if HAS_TYPEID | |
| #define TYPEID(...) typeid(__VA_ARGS__) | |
| using type_info = std::type_info; | |
| #else // ^^^ HAS_TYPEID / !HAS_TYPEID vvv | |
| #define TYPEID(...) ::any::type_info_for<__VA_ARGS__> | |
| template <class T> | |
| constexpr char const *pretty_name() noexcept | |
| { | |
| return __PRETTY_FUNCTION__; | |
| } | |
| struct type_info | |
| { | |
| type_info(type_info &&) = delete; | |
| type_info &operator=(type_info &&) = delete; | |
| constexpr explicit type_info(char const *name) noexcept | |
| : name_(name) | |
| { | |
| } | |
| constexpr const char *name() const noexcept | |
| { | |
| return name_; | |
| } | |
| [[ALWAYS_INLINE, nodiscard]] | |
| inline constexpr bool operator==(const type_info &other) const noexcept | |
| { | |
| return this == &other; | |
| } | |
| private: | |
| char const *name_; | |
| }; | |
| template <class T> | |
| inline constexpr type_info type_info_for{::any::pretty_name<T>()}; | |
| template <class T> | |
| constexpr const type_info &type_info_for<T const> = type_info_for<T>; | |
| #endif // ^^^ !HAS_TYPEID | |
| template <class...> | |
| [[deprecated]] | |
| void _print() | |
| { | |
| } | |
| template <class Return = void> | |
| [[noreturn]] | |
| inline constexpr Return _die(char const *msg, ...) noexcept | |
| { | |
| if (std::is_constant_evaluated()) | |
| { | |
| ::any::unreachable(); | |
| } | |
| else | |
| { | |
| va_list args; | |
| va_start(args, msg); | |
| std::vfprintf(stderr, msg, args); | |
| std::fflush(stderr); | |
| va_end(args); | |
| std::terminate(); | |
| } | |
| } | |
| template <class T> | |
| concept _decayed = std::same_as<std::decay_t<T>, T>; | |
| template <class Fn, class... Args> | |
| using _minvoke = typename Fn::template invoke<Args...>; | |
| template <template <class> class Template> | |
| struct mquote1 | |
| { | |
| template <class Type> | |
| using invoke = Template<Type>; | |
| }; | |
| template <class Type> | |
| using type_identity = Type; | |
| using midentity = mquote1<type_identity>; | |
| template <bool> | |
| struct _if_ | |
| { | |
| template <class Then, class...> | |
| using invoke = Then; | |
| }; | |
| template <> | |
| struct _if_<false> | |
| { | |
| template <class, class Else> | |
| using invoke = Else; | |
| }; | |
| template <bool Condition, class Then = void, class... Else> | |
| using _if_t = typename _if_<Condition>::template invoke<Then, Else...>; | |
| ////////////////////////////////////////////////////////////////////////////////////////// | |
| // _unconst | |
| template <class T> | |
| [[ALWAYS_INLINE, nodiscard]] | |
| inline constexpr T &_unconst(const T &t) noexcept | |
| { | |
| return const_cast<T &>(t); | |
| } | |
| ////////////////////////////////////////////////////////////////////////////////////////// | |
| // _const_if | |
| template <bool MakeConst, class T> | |
| using _const_if = _if_t<MakeConst, const T, T>; | |
| ////////////////////////////////////////////////////////////////////////////////////////// | |
| // forward declarations | |
| // any types | |
| template <template <class> class Interface> | |
| struct any; | |
| template <template <class> class Interface> | |
| struct any_ptr; | |
| template <template <class> class Interface> | |
| struct any_const_ptr; | |
| template <template <class> class... BaseInterfaces> | |
| struct extends; | |
| // semiregular interfaces | |
| template <class Base> | |
| struct imovable; | |
| template <class Base> | |
| struct icopyable; | |
| template <class Base> | |
| struct iequality_comparable; | |
| template <class Base> | |
| struct isemiregular; | |
| struct _iroot; | |
| template <template <class> class Interface> | |
| using _bases_of = Interface<_iroot>::bases_type; | |
| ////////////////////////////////////////////////////////////////////////////////////////// | |
| // interface_cast | |
| template <template <class> class Interface, class Base> | |
| [[ALWAYS_INLINE, nodiscard]] | |
| inline constexpr Interface<Base> &interface_cast(Interface<Base> &iface) noexcept | |
| { | |
| return iface; | |
| } | |
| template <template <class> class Interface, class Base> | |
| [[ALWAYS_INLINE, nodiscard]] | |
| inline constexpr const Interface<Base> & | |
| interface_cast(const Interface<Base> &iface) noexcept | |
| { | |
| return iface; | |
| } | |
| ////////////////////////////////////////////////////////////////////////////////////////// | |
| // accessors | |
| [[maybe_unused]] constexpr struct _value_t | |
| { | |
| template <class T> | |
| [[ALWAYS_INLINE, nodiscard]] | |
| inline constexpr auto &operator()(T &t) const noexcept | |
| { | |
| return t._value(); | |
| } | |
| } value{}; | |
| [[maybe_unused]] constexpr struct _empty_t | |
| { | |
| template <class T> | |
| [[ALWAYS_INLINE, nodiscard]] | |
| inline constexpr bool operator()(const T &t) const noexcept | |
| { | |
| return t._empty(); | |
| } | |
| } empty{}; | |
| [[maybe_unused]] constexpr struct _reset_t | |
| { | |
| template <class T> | |
| [[ALWAYS_INLINE]] | |
| inline constexpr void operator()(T &t) const noexcept | |
| { | |
| t._reset(); | |
| } | |
| } reset{}; | |
| [[maybe_unused]] constexpr struct _type_t | |
| { | |
| template <class T> | |
| [[ALWAYS_INLINE, nodiscard]] | |
| inline constexpr const type_info &operator()(const T &t) const noexcept | |
| { | |
| return t._type(); | |
| } | |
| } type{}; | |
| [[maybe_unused]] constexpr struct _data | |
| { | |
| template <class T> | |
| [[ALWAYS_INLINE, nodiscard]] | |
| inline constexpr auto *operator()(T &t) const noexcept | |
| { | |
| return t._data(); | |
| } | |
| } data{}; | |
| [[maybe_unused]] constexpr struct caddressof_t | |
| { | |
| template <template <class> class Interface, class Base> | |
| [[nodiscard]] | |
| constexpr auto operator()(const Interface<Base> &iface) const noexcept | |
| { | |
| return any_const_ptr<Interface>(std::addressof(iface)); | |
| } | |
| } caddressof{}; | |
| [[maybe_unused]] constexpr struct addressof_t : caddressof_t | |
| { | |
| using caddressof_t::operator(); | |
| template <template <class> class Interface, class Base> | |
| [[nodiscard]] | |
| constexpr auto operator()(Interface<Base> &iface) const noexcept | |
| { | |
| return any_ptr<Interface>(std::addressof(iface)); | |
| } | |
| } addressof{}; | |
| // value_of_t | |
| template <class T> | |
| using value_of_t = std::decay_t<decltype(value(std::declval<T &>()))>; | |
| ////////////////////////////////////////////////////////////////////////////////////////// | |
| // extension_of | |
| template <class Interface, template <class> class BaseInterface> | |
| concept extension_of = | |
| requires(const Interface &iface) { ::any::interface_cast<BaseInterface>(iface); }; | |
| ////////////////////////////////////////////////////////////////////////////////////////// | |
| // _is_small: Model is Interface<T> for some concrete T | |
| template <class Model> | |
| [[nodiscard]] | |
| constexpr bool _is_small(size_t buffer_size) noexcept | |
| { | |
| constexpr bool nothrow_movable = | |
| !extension_of<Model, imovable> || std::is_nothrow_move_constructible_v<Model>; | |
| return sizeof(Model) <= buffer_size && nothrow_movable; | |
| } | |
| ////////////////////////////////////////////////////////////////////////////////////////// | |
| // _tagged_ptr | |
| struct _tagged_ptr | |
| { | |
| [[ALWAYS_INLINE]] | |
| /*implicit*/ constexpr inline _tagged_ptr() noexcept | |
| : data_(std::uintptr_t(1)) | |
| { | |
| } | |
| [[ALWAYS_INLINE]] | |
| /*implicit*/ inline _tagged_ptr(void *ptr) noexcept | |
| : data_(reinterpret_cast<std::uintptr_t>(ptr) | std::uintptr_t(1)) | |
| { | |
| } | |
| [[ALWAYS_INLINE, nodiscard]] | |
| inline void *_get() const noexcept | |
| { | |
| ASSERT(!_is_vptr()); | |
| return reinterpret_cast<void *>(data_ & ~std::uintptr_t(1)); | |
| } | |
| [[ALWAYS_INLINE, nodiscard]] | |
| inline constexpr bool _is_vptr() const noexcept | |
| { | |
| return (data_ & 1) == 0; | |
| } | |
| constexpr bool operator==(std::nullptr_t) const noexcept | |
| { | |
| return data_ == 1; | |
| } | |
| std::uintptr_t data_; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////////////////// | |
| // emplace_into | |
| template <class Model, class... Args> | |
| constexpr Model &emplace_into([[maybe_unused]] _iroot *&pointer, | |
| [[maybe_unused]] std::span<unsigned char> buffer, | |
| Args &&...args) | |
| { | |
| static_assert(_decayed<Model>); | |
| if (std::is_constant_evaluated()) | |
| { | |
| pointer = ::new Model(std::forward<Args>(args)...); | |
| return *static_cast<Model *>(pointer); | |
| } | |
| else if (::any::_is_small<Model>(buffer.size())) | |
| { | |
| return *std::construct_at(reinterpret_cast<Model *>(buffer.data()), | |
| std::forward<Args>(args)...); | |
| } | |
| else | |
| { | |
| auto *const model = ::new Model(std::forward<Args>(args)...); | |
| *::any::start_lifetime_as<_tagged_ptr>(buffer.data()) = _tagged_ptr(model); | |
| return *model; | |
| } | |
| } | |
| template <int = 0, class CvRefValue, class Value = std::decay_t<CvRefValue>> | |
| constexpr Value &emplace_into(_iroot *&pointer, std::span<unsigned char> buffer, | |
| CvRefValue &&value) | |
| { | |
| return ::any::emplace_into<Value>(pointer, buffer, std::forward<CvRefValue>(value)); | |
| } | |
| // template <class Root> | |
| // concept root = requires (Root& root) | |
| // { | |
| // root._value(); | |
| // root._reset(); | |
| // { root._type() } -> std::same_as<const type_info &>; | |
| // { root._data() } -> std::same_as<void *>; | |
| // { root._empty() } -> std::same_as<bool>; | |
| // }; | |
| //! @c iabstract must be an alias in order for @c iabstract<Derived> to be | |
| //! derived from | |
| //! @c iabstract<Base>. @c iabstract<Derived> is an alias for @c | |
| //! Derived<Base<_iroot>>. | |
| template <template <class> class Interface, class BaseInterfaces = _bases_of<Interface>> | |
| using iabstract = Interface<_minvoke<BaseInterfaces, _iroot>>; | |
| // value | |
| template <template <class> class Interface, class Value> | |
| struct _value_root; | |
| template <template <class> class Interface, class Value> | |
| struct _value_model final | |
| : Interface<_minvoke<_bases_of<Interface>, _value_root<Interface, Value>>> | |
| { | |
| using _base_t = | |
| Interface<_minvoke<_bases_of<Interface>, _value_root<Interface, Value>>>; | |
| using _base_t::_base_t; | |
| // This is a virtual override if Interface extends imovable | |
| //! @pre ::any::_is_small<_value_model>(buffer.size()) | |
| constexpr void _move_to(_iroot *&pointer, std::span<unsigned char> buffer) noexcept | |
| { | |
| static_assert(extension_of<iabstract<Interface>, imovable>); | |
| ASSERT(::any::_is_small<_value_model>(buffer.size())); | |
| ::any::emplace_into(pointer, buffer, std::move(*this)); | |
| reset(*this); | |
| } | |
| // This is a virtual override if Interface extends icopyable | |
| constexpr void _copy_to(_iroot *&pointer, std::span<unsigned char> buffer) const | |
| { | |
| static_assert(extension_of<iabstract<Interface>, icopyable>); | |
| ASSERT(!empty(*this)); | |
| ::any::emplace_into(pointer, buffer, *this); | |
| } | |
| }; | |
| // value proxy | |
| template <template <class> class Interface> | |
| struct _value_proxy_root; | |
| template <template <class> class Interface> | |
| struct _value_proxy_model | |
| : Interface<_minvoke<_bases_of<Interface>, _value_proxy_root<Interface>>> | |
| { | |
| }; | |
| // reference | |
| template <template <class> class Interface, class Value> | |
| struct _reference_root; | |
| template <template <class> class Interface, class Value> | |
| struct _reference_model | |
| : Interface<_minvoke<_bases_of<Interface>, _reference_root<Interface, Value>>> | |
| { | |
| using _base_t = | |
| Interface<_minvoke<_bases_of<Interface>, _reference_root<Interface, Value>>>; | |
| using _base_t::_base_t; | |
| }; | |
| // reference proxy | |
| template <template <class> class Interface> | |
| struct _reference_proxy_root; | |
| template <template <class> class Interface> | |
| struct _reference_proxy_model | |
| : Interface<_minvoke<_bases_of<Interface>, _reference_proxy_root<Interface>>> | |
| { | |
| }; | |
| // interface | |
| enum class _box_kind | |
| { | |
| _abstract, | |
| _object, | |
| _proxy | |
| }; | |
| constexpr size_t default_buffer_size = 3 * sizeof(void *); | |
| template <class Interface, _box_kind BoxKind> | |
| concept _has_box_kind = std::remove_reference_t<Interface>::_box_kind == BoxKind; | |
| // Without the check against _has_box_kind, this concept would always be | |
| // satisfied when building an object model or a proxy model because of the | |
| // abstract implementation of BaseInterface in the iabstract layer. | |
| // | |
| // any<Derived> | |
| // : _value_proxy_model<Derived, V> | |
| // : Derived<Base<_value_proxy_root<Derived, V>>> // box_kind == object | |
| // ^^^^^^^ : Derived<Base<_iroot>> // box_kind == | |
| // abstract | |
| // ^^^^^^^ | |
| template <class Interface, template <class> class BaseInterface> | |
| concept _already_implements = requires(const Interface &iface) { | |
| { ::any::interface_cast<BaseInterface>(iface) } -> _has_box_kind<Interface::_box_kind>; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////////////////// | |
| // extends | |
| template <> | |
| struct extends<> | |
| { | |
| template <class Base> | |
| using invoke = Base; | |
| }; | |
| template <template <class> class BaseInterface, template <class> class... BaseInterfaces> | |
| struct extends<BaseInterface, BaseInterfaces...> | |
| { | |
| template <class Base, class BasesOfBase = _minvoke<_bases_of<BaseInterface>, Base>> | |
| using invoke = _minvoke<extends<BaseInterfaces...>, | |
| // If Base already implements BaseInterface, do not re-apply it. | |
| _if_t<_already_implements<Base, BaseInterface>, BasesOfBase, | |
| BaseInterface<BasesOfBase>>>; | |
| }; | |
| constexpr const char *_pure_virt_msg = "internal error: pure virtual %s() called\n"; | |
| // If we are slicing into a buffer that is smaller than our own, then slicing | |
| // may throw. | |
| template <class Interface, class Base, size_t BufferSize> | |
| concept _nothrow_slice = (!Base::_is_reference) && // | |
| (Base::_box_kind != _box_kind::_abstract) && // | |
| (Interface::buffer_size >= BufferSize); | |
| ////////////////////////////////////////////////////////////////////////////////////////// | |
| //! interface | |
| //! Specializations of @c interface are so that @c _slice and @c _bind are only | |
| //! overridden when they need to be (i.e., in the case of multiple inheritance). | |
| template <template <class> class Interface, class Base, class BaseInterfaces = extends<>, | |
| size_t BufferSize = default_buffer_size> | |
| struct interface : Base | |
| { | |
| using bases_type = BaseInterfaces; | |
| using _interface_type = iabstract<Interface, BaseInterfaces>; | |
| using Base::_bind; | |
| using Base::_slice; | |
| using Base::Base; | |
| static constexpr size_t buffer_size = | |
| BufferSize > Base::buffer_size ? BufferSize : Base::buffer_size; | |
| static constexpr bool _nothrow_slice = | |
| ::any::_nothrow_slice<_interface_type, Base, buffer_size>; | |
| constexpr ~interface() = default; | |
| [[ALWAYS_INLINE, nodiscard]] | |
| inline constexpr auto &_value() noexcept | |
| { | |
| if constexpr (Base::_box_kind == _box_kind::_abstract) | |
| return ::any::_die<_interface_type &>(_pure_virt_msg, "value"); | |
| else | |
| return Base::_value(); | |
| } | |
| [[ALWAYS_INLINE, nodiscard]] | |
| inline constexpr const auto &_value() const noexcept | |
| { | |
| if constexpr (Base::_box_kind == _box_kind::_abstract) | |
| return ::any::_die<const _interface_type &>(_pure_virt_msg, "value"); | |
| else | |
| return Base::_value(); | |
| } | |
| //! @pre Base::_box_kind != _box_kind::_proxy || !empty(*this) | |
| constexpr virtual void | |
| _slice(_value_proxy_root<Interface> &out) noexcept(_nothrow_slice) | |
| { | |
| if constexpr (Base::_box_kind == _box_kind::_abstract) | |
| { | |
| ::any::_die(_pure_virt_msg, "slice"); | |
| } | |
| else if constexpr (Base::_box_kind == _box_kind::_object) | |
| { | |
| out.emplace(std::move(value(*this))); // potentially throwing | |
| } | |
| else | |
| { | |
| value(*this)._slice(out); | |
| reset(*this); | |
| } | |
| } | |
| constexpr virtual void _bind(_reference_proxy_root<Interface> &out) const noexcept | |
| { | |
| if constexpr (Base::_box_kind == _box_kind::_abstract) | |
| { | |
| ::any::_die(_pure_virt_msg, "bind"); | |
| } | |
| else if constexpr (Base::_box_kind == _box_kind::_object) | |
| { | |
| out.rebind(value(*this)); | |
| } | |
| else | |
| { | |
| ASSERT(!empty(*this)); | |
| value(*this)._bind(out); | |
| } | |
| } | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////////////////// | |
| // _iroot | |
| struct _iroot | |
| { | |
| static constexpr ::any::_box_kind _box_kind = ::any::_box_kind::_abstract; | |
| static constexpr bool _is_reference = false; | |
| static constexpr size_t buffer_size = sizeof(_tagged_ptr); // minimum size | |
| using bases_type = extends<>; | |
| // needed by MSVC for EBO to work for some reason: | |
| constexpr virtual ~_iroot() = default; | |
| [[nodiscard]] | |
| constexpr virtual bool _empty() const noexcept | |
| { | |
| return ::any::_die<bool>(_pure_virt_msg, "empty"); | |
| } | |
| constexpr virtual void _reset() noexcept | |
| { | |
| ::any::_die(_pure_virt_msg, "reset"); | |
| } | |
| [[nodiscard]] | |
| constexpr virtual const type_info &_type() const noexcept | |
| { | |
| return ::any::_die<const type_info &>(_pure_virt_msg, "type"); | |
| } | |
| [[nodiscard]] | |
| constexpr virtual void *_data() const noexcept | |
| { | |
| return ::any::_die<void *>(_pure_virt_msg, "data"); | |
| } | |
| void _slice() noexcept = delete; | |
| void _bind() const noexcept = delete; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////////////////// | |
| // _value_root | |
| template <template <class> class Interface, class Value> | |
| struct _value_root : iabstract<Interface> | |
| { | |
| using interface_type = iabstract<Interface>; | |
| static constexpr ::any::_box_kind _box_kind = ::any::_box_kind::_object; | |
| constexpr explicit _value_root(Value val) noexcept | |
| : value(std::move(val)) | |
| { | |
| } | |
| [[nodiscard]] | |
| constexpr Value &_value() noexcept | |
| { | |
| return value; | |
| } | |
| [[nodiscard]] | |
| constexpr const Value &_value() const noexcept | |
| { | |
| return value; | |
| } | |
| [[nodiscard]] | |
| constexpr bool _empty() const noexcept final override | |
| { | |
| return false; | |
| } | |
| constexpr void _reset() noexcept final override | |
| { | |
| // no-op | |
| } | |
| [[nodiscard]] | |
| constexpr const type_info &_type() const noexcept final override | |
| { | |
| return TYPEID(Value); | |
| } | |
| [[nodiscard]] | |
| constexpr void *_data() const noexcept final override | |
| { | |
| return const_cast<void *>(static_cast<const void *>(std::addressof(_value()))); | |
| } | |
| Value value; | |
| }; | |
| // A specialization of _value_root to take advantage of EBO (empty base | |
| // optimization): | |
| template <template <class> class Interface, class Value> | |
| requires std::is_empty_v<Value> && (!std::is_final_v<Value>) | |
| struct [[EMPTY_BASES]] _value_root<Interface, Value> | |
| : iabstract<Interface> | |
| , private Value | |
| { | |
| using interface_type = iabstract<Interface>; | |
| static constexpr ::any::_box_kind _box_kind = ::any::_box_kind::_object; | |
| constexpr explicit _value_root(Value val) noexcept | |
| : Value(std::move(val)) | |
| { | |
| } | |
| [[nodiscard]] | |
| constexpr Value &_value() noexcept | |
| { | |
| return *this; | |
| } | |
| [[nodiscard]] | |
| constexpr const Value &_value() const noexcept | |
| { | |
| return *this; | |
| } | |
| [[nodiscard]] | |
| constexpr bool _empty() const noexcept final override | |
| { | |
| return false; | |
| } | |
| constexpr void _reset() noexcept final override | |
| { | |
| // no-op | |
| } | |
| [[nodiscard]] | |
| constexpr const type_info &_type() const noexcept final override | |
| { | |
| return TYPEID(Value); | |
| } | |
| [[nodiscard]] | |
| constexpr void *_data() const noexcept final override | |
| { | |
| return const_cast<void *>(static_cast<const void *>(std::addressof(_value()))); | |
| } | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////////////////// | |
| // _value_proxy_root | |
| template <template <class> class Interface> | |
| struct _value_proxy_root : iabstract<Interface> | |
| { | |
| using interface_type = iabstract<Interface>; | |
| static constexpr ::any::_box_kind _box_kind = ::any::_box_kind::_proxy; | |
| static constexpr bool _movable = extension_of<iabstract<Interface>, imovable>; | |
| static constexpr bool _copyable = extension_of<iabstract<Interface>, icopyable>; | |
| [[ALWAYS_INLINE]] | |
| inline constexpr _value_proxy_root() noexcept | |
| { | |
| if (std::is_constant_evaluated()) | |
| pointer = nullptr; | |
| else | |
| *::any::start_lifetime_as<_tagged_ptr>(buffer) = _tagged_ptr(); | |
| } | |
| constexpr _value_proxy_root(_value_proxy_root &&other) noexcept | |
| requires _movable | |
| : _value_proxy_root() | |
| { | |
| swap(other); | |
| } | |
| constexpr _value_proxy_root(_value_proxy_root const &other) | |
| requires _copyable | |
| : _value_proxy_root() | |
| { | |
| if (!empty(other)) | |
| value(other)._copy_to(pointer, buffer); | |
| } | |
| constexpr ~_value_proxy_root() | |
| { | |
| _reset(); | |
| } | |
| constexpr _value_proxy_root &operator=(_value_proxy_root &&other) noexcept | |
| requires _movable | |
| { | |
| if (this != &other) | |
| { | |
| _reset(); | |
| swap(other); | |
| } | |
| return *this; | |
| } | |
| constexpr _value_proxy_root &operator=(_value_proxy_root const &other) | |
| requires _copyable | |
| { | |
| if (this != &other) | |
| _value_proxy_root(other).swap(*this); | |
| return *this; | |
| } | |
| constexpr void swap(_value_proxy_root &other) noexcept | |
| requires _movable | |
| { | |
| if (std::is_constant_evaluated()) | |
| { | |
| std::swap(pointer, other.pointer); | |
| } | |
| else | |
| { | |
| if (this == &other) | |
| return; | |
| auto &this_ptr = *::any::start_lifetime_as<_tagged_ptr>(buffer); | |
| auto &that_ptr = *::any::start_lifetime_as<_tagged_ptr>(other.buffer); | |
| // This also covers the case where both this_ptr and that_ptr are null. | |
| if (!this_ptr._is_vptr() && !that_ptr._is_vptr()) | |
| return std::swap(this_ptr, that_ptr); | |
| if (this_ptr == nullptr) | |
| return value(other)._move_to(pointer, buffer); | |
| if (that_ptr == nullptr) | |
| return value(*this)._move_to(other.pointer, other.buffer); | |
| auto temp = std::move(*this); | |
| value(other)._move_to(pointer, buffer); | |
| value(temp)._move_to(other.pointer, other.buffer); | |
| } | |
| } | |
| template <class Value, class... Args> | |
| constexpr Value &_emplace(Args &&...args) | |
| { | |
| static_assert(_decayed<Value>, "Value must be an object type."); | |
| using model_type = _value_model<Interface, Value>; | |
| auto &model = | |
| ::any::emplace_into<model_type>(pointer, buffer, std::forward<Args>(args)...); | |
| return model._value(); | |
| } | |
| template <int = 0, class CvRefValue, class Value = std::decay_t<CvRefValue>> | |
| constexpr Value &_emplace(CvRefValue &&value) | |
| { | |
| return _emplace<Value>(std::forward<CvRefValue>(value)); | |
| } | |
| template <class Value, class... Args> | |
| constexpr Value &emplace(Args &&...args) | |
| { | |
| _reset(); | |
| return _emplace<Value>(std::forward<Args>(args)...); | |
| } | |
| template <int = 0, class CvRefValue, class Value = std::decay_t<CvRefValue>> | |
| constexpr Value &emplace(CvRefValue &&value) | |
| { | |
| _reset(); | |
| return _emplace<Value>(std::forward<CvRefValue>(value)); | |
| } | |
| [[nodiscard]] | |
| constexpr iabstract<Interface> &_value() noexcept | |
| { | |
| if (std::is_constant_evaluated()) | |
| { | |
| return *static_cast<iabstract<Interface> *>(pointer); | |
| } | |
| else | |
| { | |
| const auto ptr = *::any::start_lifetime_as<_tagged_ptr>(buffer); | |
| ASSERT(ptr != nullptr); | |
| return *static_cast<iabstract<Interface> *>(ptr._is_vptr() ? buffer : ptr._get()); | |
| } | |
| } | |
| [[nodiscard]] | |
| constexpr const iabstract<Interface> &_value() const noexcept | |
| { | |
| if (std::is_constant_evaluated()) | |
| { | |
| return *static_cast<const iabstract<Interface> *>(pointer); | |
| } | |
| else | |
| { | |
| const auto ptr = *::any::start_lifetime_as<_tagged_ptr>(buffer); | |
| ASSERT(ptr != nullptr); | |
| return *static_cast<const iabstract<Interface> *>(ptr._is_vptr() ? buffer | |
| : ptr._get()); | |
| } | |
| } | |
| [[nodiscard]] | |
| constexpr bool _empty() const noexcept final override | |
| { | |
| if (std::is_constant_evaluated()) | |
| { | |
| return pointer == nullptr; | |
| } | |
| else | |
| { | |
| return *::any::start_lifetime_as<_tagged_ptr>(buffer) == nullptr; | |
| } | |
| } | |
| [[ALWAYS_INLINE]] | |
| inline constexpr void _reset() noexcept final override | |
| { | |
| if (std::is_constant_evaluated()) | |
| { | |
| delete static_cast<iabstract<Interface> *>(std::exchange(pointer, {})); | |
| } | |
| else | |
| { | |
| auto &ptr = *::any::start_lifetime_as<_tagged_ptr>(buffer); | |
| if (ptr == nullptr) | |
| return; | |
| else if (ptr._is_vptr()) | |
| std::destroy_at(std::addressof(_value())); | |
| else | |
| delete std::addressof(_value()); | |
| ptr = _tagged_ptr(); | |
| } | |
| } | |
| [[nodiscard]] | |
| constexpr const type_info &_type() const noexcept final override | |
| { | |
| return _empty() ? TYPEID(void) : type(_value()); | |
| } | |
| [[nodiscard]] | |
| constexpr void *_data() const noexcept final override | |
| { | |
| return _empty() ? nullptr : data(_value()); | |
| } | |
| [[nodiscard]] | |
| constexpr bool _in_situ() const noexcept | |
| { | |
| if (std::is_constant_evaluated()) | |
| return false; | |
| else | |
| return ::any::start_lifetime_as<_tagged_ptr>(buffer)->_is_vptr(); | |
| } | |
| union | |
| { | |
| _iroot *pointer; | |
| unsigned char buffer[iabstract<Interface>::buffer_size]; | |
| }; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////////////////// | |
| // _reference_root | |
| template <template <class> class Interface, class Value> | |
| struct _reference_root : iabstract<Interface> | |
| { | |
| using interface_type = iabstract<Interface>; | |
| static constexpr ::any::_box_kind _box_kind = ::any::_box_kind::_object; | |
| static constexpr bool _is_reference = true; | |
| constexpr explicit _reference_root(Value &value) noexcept | |
| : value_(std::addressof(value)) | |
| { | |
| } | |
| [[nodiscard]] | |
| constexpr auto &_value() noexcept | |
| { | |
| if constexpr (std::is_const_v<Value>) | |
| ::any::_die("attempt to obtain mutable reference from const reference\n"); | |
| return ::any::_unconst(*value_); | |
| } | |
| [[nodiscard]] | |
| constexpr auto &_value() const noexcept | |
| { | |
| return *value_; | |
| } | |
| [[nodiscard]] | |
| constexpr bool _empty() const noexcept final override | |
| { | |
| return false; | |
| } | |
| constexpr void _reset() noexcept final override | |
| { | |
| // no-op | |
| } | |
| [[nodiscard]] | |
| constexpr const type_info &_type() const noexcept final override | |
| { | |
| return TYPEID(Value); | |
| } | |
| [[nodiscard]] | |
| constexpr void *_data() const noexcept final override | |
| { | |
| return const_cast<void *>(static_cast<const void *>(value_)); | |
| } | |
| private: | |
| Value *value_; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////////////////// | |
| // _reference_proxy_root | |
| template <template <class> class Interface> | |
| struct _reference_proxy_root : iabstract<Interface> | |
| { | |
| using interface_type = iabstract<Interface>; | |
| static constexpr ::any::_box_kind _box_kind = ::any::_box_kind::_proxy; | |
| static constexpr bool _is_reference = true; | |
| constexpr _reference_proxy_root() noexcept | |
| { | |
| if (std::is_constant_evaluated()) | |
| pointer = nullptr; | |
| else | |
| *::any::start_lifetime_as<_tagged_ptr>(buffer) = _tagged_ptr(); | |
| } | |
| constexpr ~_reference_proxy_root() | |
| { | |
| if (std::is_constant_evaluated()) | |
| _reset(); | |
| } | |
| constexpr void swap(_reference_proxy_root &other) noexcept | |
| { | |
| if (this != &other) | |
| { | |
| if (std::is_constant_evaluated()) | |
| std::swap(pointer, other.pointer); | |
| else | |
| std::swap(buffer, other.buffer); | |
| } | |
| } | |
| template <class CvValue> | |
| constexpr CvValue &rebind(CvValue &val) noexcept | |
| { | |
| using model_type = _reference_model<Interface, CvValue>; | |
| if (std::is_constant_evaluated()) | |
| { | |
| // TODO(ericniebler): try to avoid allocation here? | |
| pointer = ::new model_type(val); | |
| } | |
| else if constexpr (std::derived_from<CvValue, iabstract<Interface>>) | |
| { | |
| //! Optimize for when Base derives from iabstract<Interface>. Store the | |
| //! address of value(other) directly in out as a tagged ptr instead of | |
| //! introducing an indirection. | |
| //! @post _is_vptr() == false | |
| auto &ptr = *::any::start_lifetime_as<_tagged_ptr>(buffer); | |
| ptr = static_cast<iabstract<Interface> *>(std::addressof(::any::_unconst(val))); | |
| } | |
| else | |
| { | |
| //! @post _is_vptr() == true | |
| ::any::emplace_into<model_type>(pointer, buffer, val); | |
| } | |
| return val; | |
| } | |
| [[nodiscard]] | |
| constexpr iabstract<Interface> &_value() noexcept | |
| { | |
| if (std::is_constant_evaluated()) | |
| { | |
| return *static_cast<iabstract<Interface> *>(pointer); | |
| } | |
| else | |
| { | |
| ASSERT(!_empty()); | |
| const auto ptr = *::any::start_lifetime_as<_tagged_ptr>(buffer); | |
| return *static_cast<iabstract<Interface> *>(ptr._is_vptr() ? buffer : ptr._get()); | |
| } | |
| } | |
| [[nodiscard]] | |
| constexpr const iabstract<Interface> &_value() const noexcept | |
| { | |
| if (std::is_constant_evaluated()) | |
| { | |
| return *static_cast<const iabstract<Interface> *>(pointer); | |
| } | |
| else | |
| { | |
| ASSERT(!_empty()); | |
| const auto ptr = *::any::start_lifetime_as<_tagged_ptr>(buffer); | |
| return *static_cast<const iabstract<Interface> *>(ptr._is_vptr() ? buffer | |
| : ptr._get()); | |
| } | |
| } | |
| [[nodiscard]] | |
| constexpr bool _empty() const noexcept final override | |
| { | |
| if (std::is_constant_evaluated()) | |
| return pointer == nullptr; | |
| else | |
| return *::any::start_lifetime_as<_tagged_ptr>(buffer) == nullptr; | |
| } | |
| constexpr void _reset() noexcept final override | |
| { | |
| if (std::is_constant_evaluated()) | |
| delete static_cast<iabstract<Interface> *>(std::exchange(pointer, {})); | |
| else | |
| *::any::start_lifetime_as<_tagged_ptr>(buffer) = _tagged_ptr(); | |
| } | |
| [[nodiscard]] | |
| constexpr const type_info &_type() const noexcept final override | |
| { | |
| return _empty() ? TYPEID(void) : type(_value()); | |
| } | |
| [[nodiscard]] | |
| constexpr void *_data() const noexcept final override | |
| { | |
| return _empty() ? nullptr : data(_value()); | |
| } | |
| [[nodiscard]] | |
| constexpr bool _indirect() const noexcept | |
| { | |
| if (std::is_constant_evaluated()) | |
| return true; | |
| else | |
| return ::any::start_lifetime_as<_tagged_ptr>(buffer)->_is_vptr(); | |
| } | |
| private: | |
| // storage for one vtable ptr and one pointer for the referant | |
| union | |
| { | |
| _iroot *pointer; | |
| mutable unsigned char buffer[2 * sizeof(void *)]{}; | |
| }; | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////////////////// | |
| // bad_any_cast | |
| struct bad_any_cast : std::exception | |
| { | |
| [[nodiscard]] | |
| constexpr const char *what() const noexcept override | |
| { | |
| return "bad_any_cast"; | |
| } | |
| }; | |
| #if __cpp_exceptions | |
| [[noreturn]] | |
| inline void _throw_bad_any_cast() | |
| { | |
| throw bad_any_cast(); | |
| } | |
| #else | |
| [[noreturn]] | |
| inline constexpr void _throw_bad_any_cast() noexcept | |
| { | |
| ::any::_die("bad_any_cast\n"); | |
| } | |
| #endif | |
| ////////////////////////////////////////////////////////////////////////////////////////// | |
| // _polymorphic_downcast | |
| template <class Root, class Interface> | |
| [[nodiscard]] | |
| constexpr Root _polymorphic_downcast(Interface *from) noexcept | |
| requires(std::is_pointer_v<Root>) | |
| { | |
| using root_type = _const_if<std::is_const_v<Interface>, std::remove_pointer_t<Root>>; | |
| using value_type [[maybe_unused]] = std::decay_t<decltype(::any::value(*Root()))>; | |
| static_assert(std::derived_from<root_type, Interface>, | |
| "_polymorphic_downcast requires From to be a base class of To"); | |
| #if __cpp_rtti | |
| ASSERT(dynamic_cast<root_type *>(from) != nullptr); | |
| #else | |
| ASSERT(type(*from) == TYPEID(value_type)); | |
| #endif | |
| return static_cast<root_type *>(from); | |
| } | |
| ////////////////////////////////////////////////////////////////////////////////////////// | |
| // _value_ptr | |
| template <class Root> | |
| [[nodiscard]] | |
| constexpr auto *_value_ptr(Root *root) noexcept | |
| { | |
| return root != nullptr ? std::addressof(value(*root)) : nullptr; | |
| } | |
| ////////////////////////////////////////////////////////////////////////////////////////// | |
| //! _any_dynamic_cast | |
| //! @tparam Value The target type to cast to. Must be _decayed. | |
| //! @tparam Interface The interface template the source any type implements. | |
| //! @tparam From The possibly const-qualified Interface<Model> type. | |
| template <class Value, template <class> class Interface, class From> | |
| [[nodiscard]] | |
| constexpr auto *_any_dynamic_cast(From *ptr) noexcept | |
| { | |
| auto *val = std::addressof(value(*ptr)); // get the address of the model from the proxy | |
| if constexpr (extension_of<Value, Interface>) | |
| { | |
| return val; | |
| } | |
| else if constexpr (!From::_is_reference) | |
| { | |
| using value_model = _const_if<std::is_const_v<From>, _value_root<Interface, Value>>; | |
| return ::any::_value_ptr(::any::_polymorphic_downcast<value_model *>(val)); | |
| } | |
| else | |
| { | |
| using value_model = _const_if<std::is_const_v<From>, _value_root<Interface, Value>>; | |
| using referant_type = _const_if<std::is_const_v<From>, Value>; | |
| using reference_model = | |
| _const_if<std::is_const_v<From>, _reference_root<Interface, referant_type>>; | |
| return ptr->_indirect() | |
| ? ::any::_value_ptr(::any::_polymorphic_downcast<reference_model *>(val)) | |
| : ::any::_value_ptr(::any::_polymorphic_downcast<value_model *>(val)); | |
| } | |
| } | |
| ////////////////////////////////////////////////////////////////////////////////////////// | |
| // any_cast | |
| template <class Value> | |
| struct any_cast_t | |
| { | |
| static_assert(_decayed<Value>); | |
| template <template <class> class Interface, class Base> | |
| [[nodiscard]] | |
| constexpr auto *operator()(Interface<Base> *ptr) const noexcept | |
| { | |
| return ptr != nullptr && !empty(*ptr) | |
| ? ::any::_any_dynamic_cast<Value, Interface>(ptr) | |
| : nullptr; | |
| } | |
| template <template <class> class Interface, class Base> | |
| [[nodiscard]] | |
| constexpr auto *operator()(const Interface<Base> *ptr) const noexcept | |
| { | |
| return ptr != nullptr && !empty(*ptr) | |
| ? ::any::_any_dynamic_cast<Value, Interface>(ptr) | |
| : nullptr; | |
| } | |
| template <template <class> class Interface, class Base> | |
| [[nodiscard]] | |
| constexpr auto &&operator()(Interface<Base> &&object) const | |
| { | |
| auto *ptr = (*this)(std::addressof(object)); | |
| if (ptr == nullptr) | |
| _throw_bad_any_cast(); | |
| if constexpr (Base::_is_reference) | |
| return *ptr; | |
| else | |
| return std::move(*ptr); | |
| } | |
| template <template <class> class Interface, class Base> | |
| [[nodiscard]] | |
| constexpr auto &operator()(Interface<Base> &object) const | |
| { | |
| auto *ptr = (*this)(std::addressof(object)); | |
| if (ptr == nullptr) | |
| _throw_bad_any_cast(); | |
| return *ptr; | |
| } | |
| template <template <class> class Interface, class Base> | |
| [[nodiscard]] | |
| constexpr auto &operator()(const Interface<Base> &object) const | |
| { | |
| auto *ptr = (*this)(std::addressof(object)); | |
| if (ptr == nullptr) | |
| _throw_bad_any_cast(); | |
| return *ptr; | |
| } | |
| template <template <class> class Interface> | |
| [[nodiscard]] | |
| constexpr auto *operator()(const any_ptr<Interface> &ptr) const | |
| { | |
| return (*this)(ptr.operator->()); | |
| } | |
| template <template <class> class Interface> | |
| [[nodiscard]] | |
| constexpr auto *operator()(const any_const_ptr<Interface> &ptr) const | |
| { | |
| return (*this)(ptr.operator->()); | |
| } | |
| }; | |
| template <class Value> | |
| constexpr any_cast_t<Value> any_cast{}; | |
| ////////////////////////////////////////////////////////////////////////////////////////// | |
| // any_static_cast | |
| template <class Value> | |
| struct any_static_cast_t | |
| { | |
| static_assert(_decayed<Value>); | |
| template <template <class> class Interface, class Base> | |
| [[nodiscard]] | |
| constexpr auto *operator()(Interface<Base> *ptr) const noexcept | |
| { | |
| if constexpr (extension_of<Value, Interface>) | |
| return ptr; | |
| else if (ptr == nullptr || empty(*ptr)) | |
| return static_cast<Value *>(nullptr); | |
| #if __cpp_constexpr < 2023'06L // C++26 constexpr void* casts (https://wg21.link/P2738R1) | |
| else if (std::is_constant_evaluated()) | |
| return ::any::_any_dynamic_cast<Value, Interface>(ptr); | |
| #endif | |
| else | |
| return static_cast<Value *>(data(*ptr)); | |
| } | |
| template <template <class> class Interface, class Base> | |
| [[nodiscard]] | |
| constexpr auto *operator()(const Interface<Base> *ptr) const noexcept | |
| { | |
| if constexpr (extension_of<Value, Interface>) | |
| return ptr; | |
| else if (ptr == nullptr || empty(*ptr)) | |
| return static_cast<const Value *>(nullptr); | |
| #if __cpp_constexpr < 2023'06L // C++26 constexpr void* casts (https://wg21.link/P2738R1) | |
| else if (std::is_constant_evaluated()) | |
| return ::any::_any_dynamic_cast<Value, Interface>(ptr); | |
| #endif | |
| else | |
| return static_cast<const Value *>(data(*ptr)); | |
| } | |
| template <template <class> class Interface, class Base> | |
| [[nodiscard]] | |
| constexpr auto &&operator()(Interface<Base> &&object) const noexcept | |
| { | |
| ASSERT(!empty(object)); | |
| if constexpr (Base::_is_reference) | |
| return *(*this)(std::addressof(object)); | |
| else | |
| return std::move(*(*this)(&object)); | |
| } | |
| template <template <class> class Interface, class Base> | |
| [[nodiscard]] | |
| constexpr auto &operator()(Interface<Base> &object) const noexcept | |
| { | |
| ASSERT(!empty(object)); | |
| return *(*this)(std::addressof(object)); | |
| } | |
| template <template <class> class Interface, class Base> | |
| [[nodiscard]] | |
| constexpr auto &operator()(const Interface<Base> &object) const noexcept | |
| { | |
| ASSERT(!empty(object)); | |
| return *(*this)(std::addressof(object)); | |
| } | |
| template <template <class> class Interface> | |
| [[nodiscard]] | |
| constexpr auto *operator()(const any_ptr<Interface> &ptr) const noexcept | |
| { | |
| return (*this)(ptr.operator->()); | |
| } | |
| template <template <class> class Interface> | |
| [[nodiscard]] | |
| constexpr auto *operator()(const any_const_ptr<Interface> &ptr) const noexcept | |
| { | |
| return (*this)(ptr.operator->()); | |
| } | |
| }; | |
| template <class Value> | |
| constexpr any_static_cast_t<Value> any_static_cast{}; | |
| ////////////////////////////////////////////////////////////////////////////////////////// | |
| // imovable | |
| template <class Base> | |
| struct imovable : interface<imovable, Base> | |
| { | |
| using imovable::interface::interface; | |
| constexpr virtual void _move_to(_iroot *&, std::span<unsigned char>) noexcept | |
| { | |
| ::any::_die(_pure_virt_msg, "_move_to"); | |
| } | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////////////////// | |
| // icopyable | |
| template <class Base> | |
| struct icopyable : interface<icopyable, Base, extends<imovable>> | |
| { | |
| using icopyable::interface::interface; | |
| constexpr virtual void _copy_to(_iroot *&, std::span<unsigned char>) const | |
| { | |
| ::any::_die(_pure_virt_msg, "_copy_to"); | |
| } | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////////////////// | |
| // utils | |
| template <class Value, template <class> class Interface> | |
| concept _model_of = _decayed<Value> && !std::derived_from<Value, _iroot>; | |
| ////////////////////////////////////////////////////////////////////////////////////////// | |
| // any | |
| template <template <class> class Interface> | |
| struct any final : _value_proxy_model<Interface> | |
| { | |
| private: | |
| template <class Other> | |
| static constexpr bool _as_large_as = | |
| iabstract<Interface>::buffer_size >= Interface<Other>::buffer_size && | |
| !Other::_is_reference; | |
| public: | |
| any() = default; | |
| // Construct from an object that implements the interface (and is not an any<> | |
| // itself) | |
| template <_model_of<Interface> Value> | |
| constexpr any(Value value) | |
| : any() | |
| { | |
| (*this)._emplace(std::move(value)); | |
| } | |
| // Implicit derived-to-base conversion constructor | |
| template <class Other> | |
| constexpr any(Interface<Other> other) noexcept(_as_large_as<Other>) | |
| requires extension_of<Interface<Other>, imovable> | |
| { | |
| _assign(std::move(other)); | |
| } | |
| template <_model_of<Interface> Value> | |
| constexpr any &operator=(Value value) | |
| { | |
| reset(*this); | |
| (*this)._emplace(std::move(value)); | |
| return *this; | |
| } | |
| // Implicit derived-to-base conversion constructor | |
| template <class Other> | |
| constexpr any &operator=(Interface<Other> other) noexcept(_as_large_as<Other>) | |
| requires extension_of<Interface<Other>, imovable> | |
| { | |
| // Guard against self-assignment when other is a reference to *this | |
| if constexpr (Other::_is_reference) | |
| if (std::addressof(value(other)) == std::addressof(value(*this))) | |
| return *this; | |
| reset(*this); | |
| _assign(std::move(other)); | |
| return *this; | |
| } | |
| friend constexpr void swap(any &a, any &b) noexcept | |
| requires any::_movable | |
| { | |
| a.swap(b); | |
| } | |
| private: | |
| // Assigning from a type that extends Interface. Its buffer may be larger than | |
| // ours, or it may be a reference type, so we can be only conditionally | |
| // noexcept. | |
| template <class Other> | |
| constexpr void _assign(Interface<Other> &&other) noexcept(_as_large_as<Other>) | |
| requires extension_of<Interface<Other>, imovable> | |
| { | |
| constexpr bool ptr_convertible = std::derived_from<Other, iabstract<Interface>>; | |
| if (empty(other)) | |
| { | |
| return; | |
| } | |
| else if constexpr (Other::_is_reference || !ptr_convertible) | |
| { | |
| return other._slice(*this); | |
| } | |
| else if (other._in_situ()) | |
| { | |
| return other._slice(*this); | |
| } | |
| else if (std::is_constant_evaluated()) | |
| { | |
| (*this).pointer = std::exchange(other.pointer, nullptr); | |
| } | |
| else | |
| { | |
| auto &ptr = *::any::start_lifetime_as<_tagged_ptr>((*this).buffer); | |
| ptr = *::any::start_lifetime_as<_tagged_ptr>(other.buffer); | |
| } | |
| } | |
| static_assert(sizeof(iabstract<Interface>) == sizeof(void *)); // sanity check | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////////////////// | |
| // any_ptr | |
| template <template <class> class Interface> | |
| struct any_ptr final : any_const_ptr<Interface> | |
| { | |
| any_ptr() = default; | |
| any_ptr(const any_ptr &other) noexcept = default; | |
| any_ptr &operator=(const any_ptr &other) noexcept = default; | |
| bool operator==(const any_ptr &other) const noexcept = default; | |
| constexpr any_ptr(std::nullptr_t) noexcept | |
| : any_const_ptr<Interface>() | |
| { | |
| } | |
| template <_model_of<Interface> Value> | |
| constexpr any_ptr(Value *ptr) noexcept | |
| : any_const_ptr<Interface>() | |
| { | |
| (*this)._assign(ptr); | |
| } | |
| template <template <class> class OtherInterface> | |
| requires extension_of<iabstract<OtherInterface>, Interface> | |
| constexpr any_ptr(const any_ptr<OtherInterface> &other) noexcept | |
| : any_const_ptr<Interface>() | |
| { | |
| (*this)._assign(other.operator->()); | |
| } | |
| template <class Base> | |
| constexpr any_ptr(Interface<Base> *other) noexcept | |
| : any_const_ptr<Interface>() | |
| { | |
| (*this)._assign(other); | |
| } | |
| template <_model_of<Interface> Value> | |
| constexpr any_ptr &operator=(Value *ptr) noexcept | |
| { | |
| reset((*this).base_); | |
| (*this)._assign(ptr); | |
| return *this; | |
| } | |
| template <class Base> | |
| constexpr any_ptr &operator=(Interface<Base> *other) noexcept | |
| { | |
| reset((*this).base_); | |
| (*this)._assign(other); | |
| return *this; | |
| } | |
| template <template <class> class OtherInterface> | |
| requires extension_of<iabstract<OtherInterface>, Interface> | |
| constexpr any_ptr &operator=(const any_ptr<OtherInterface> &other) noexcept | |
| { | |
| reset((*this).base_); | |
| (*this)._assign(other.operator->()); | |
| return *this; | |
| } | |
| [[ALWAYS_INLINE, nodiscard]] | |
| inline constexpr auto *operator->() const noexcept | |
| { | |
| return &(*this).base_; | |
| } | |
| [[ALWAYS_INLINE, nodiscard]] | |
| inline constexpr auto &operator*() const noexcept | |
| { | |
| return (*this).base_; | |
| } | |
| }; | |
| template <template <class> class Interface, class Base> | |
| any_ptr(Interface<Base> *) -> any_ptr<Interface>; | |
| ////////////////////////////////////////////////////////////////////////////////////////// | |
| // any_const_ptr | |
| template <template <class> class Interface> | |
| struct any_const_ptr | |
| { | |
| any_const_ptr() = default; | |
| constexpr any_const_ptr(const any_const_ptr &other) noexcept | |
| : base_() | |
| { | |
| _assign(std::addressof(other.base_)); | |
| } | |
| constexpr any_const_ptr(std::nullptr_t) noexcept | |
| : base_() | |
| { | |
| } | |
| template <_model_of<Interface> Value> | |
| constexpr any_const_ptr(const Value *ptr) noexcept | |
| : base_() | |
| { | |
| _assign(ptr); | |
| } | |
| template <template <class> class OtherInterface> | |
| requires extension_of<iabstract<OtherInterface>, Interface> | |
| constexpr any_const_ptr(const any_ptr<OtherInterface> &other) noexcept | |
| : base_() | |
| { | |
| _assign(other.operator->()); | |
| } | |
| template <template <class> class OtherInterface> | |
| requires extension_of<iabstract<OtherInterface>, Interface> | |
| constexpr any_const_ptr(const any_const_ptr<OtherInterface> &other) noexcept | |
| : base_() | |
| { | |
| _assign(other.operator->()); | |
| } | |
| template <class Base> | |
| constexpr any_const_ptr(const Interface<Base> *other) noexcept | |
| : base_() | |
| { | |
| _assign(other); | |
| } | |
| constexpr any_const_ptr &operator=(const any_const_ptr &other) noexcept | |
| { | |
| reset(base_); | |
| _assign(std::addressof(other.base_)); | |
| return *this; | |
| } | |
| constexpr any_const_ptr &operator=(std::nullptr_t) noexcept | |
| { | |
| reset(base_); | |
| return *this; | |
| } | |
| template <_model_of<Interface> Value> | |
| constexpr any_const_ptr &operator=(const Value *ptr) noexcept | |
| { | |
| reset(base_); | |
| _assign(ptr); | |
| return *this; | |
| } | |
| template <template <class> class OtherInterface> | |
| requires extension_of<iabstract<OtherInterface>, Interface> | |
| constexpr any_const_ptr &operator=(const any_ptr<OtherInterface> &other) noexcept | |
| { | |
| reset(base_); | |
| _assign(other.operator->()); | |
| return *this; | |
| } | |
| template <template <class> class OtherInterface> | |
| requires extension_of<iabstract<OtherInterface>, Interface> | |
| constexpr any_const_ptr &operator=(const any_const_ptr<OtherInterface> &other) noexcept | |
| { | |
| reset(base_); | |
| _assign(other.operator->()); | |
| return *this; | |
| } | |
| template <class Base> | |
| constexpr any_const_ptr &operator=(const Interface<Base> *other) noexcept | |
| { | |
| reset(base_); | |
| _assign(other); | |
| return *this; | |
| } | |
| friend constexpr void swap(any_const_ptr &a, any_const_ptr &b) noexcept | |
| { | |
| a.base_.swap(b.base_); | |
| } | |
| [[ALWAYS_INLINE, nodiscard]] | |
| inline constexpr const auto *operator->() const noexcept | |
| { | |
| return std::addressof(base_); | |
| } | |
| [[ALWAYS_INLINE, nodiscard]] | |
| inline constexpr const auto &operator*() const noexcept | |
| { | |
| return base_; | |
| } | |
| constexpr bool operator==(const any_const_ptr &other) const noexcept | |
| { | |
| return data(base_) == data(other.base_); | |
| } | |
| private: | |
| template <extension_of<Interface> CvDerived> | |
| constexpr void _assign(CvDerived *other) noexcept | |
| { | |
| if (other != nullptr && !empty(*other)) | |
| { | |
| // Optimize for when CvDerived derives from iabstract<Interface>. Store | |
| // the address of value(other) directly in out as a tagged ptr instead of | |
| // introducing an indirection. | |
| if constexpr (!std::derived_from<CvDerived, iabstract<Interface>>) | |
| (*other)._bind(base_); | |
| else if constexpr (std::is_const_v<CvDerived>) | |
| base_.rebind(std::as_const(value(*other))); | |
| else | |
| base_.rebind(value(*other)); | |
| } | |
| } | |
| template <class CvValue> | |
| constexpr void _assign(CvValue *ptr) noexcept | |
| { | |
| reset(base_); | |
| if (ptr != nullptr) | |
| base_.rebind(*ptr); | |
| } | |
| static_assert(sizeof(iabstract<Interface>) == sizeof(void *)); // sanity check | |
| friend struct any_ptr<Interface>; | |
| // the proxy model is mutable so that a const any_ptr can return non-const | |
| // references from operator-> and operator*. | |
| mutable _reference_proxy_model<Interface> base_; | |
| }; | |
| template <template <class> class Interface, class Base> | |
| any_const_ptr(const Interface<Base> *) -> any_const_ptr<Interface>; | |
| ////////////////////////////////////////////////////////////////////////////////////////// | |
| // iequality_comparable | |
| template <class Base> | |
| struct iequality_comparable : interface<iequality_comparable, Base> | |
| { | |
| using iequality_comparable::interface::interface; | |
| template <class Other> | |
| constexpr bool operator==(const iequality_comparable<Other> &other) const | |
| { | |
| return _equal_to(::any::caddressof(other)); | |
| } | |
| private: | |
| constexpr virtual bool _equal_to(any_const_ptr<iequality_comparable> other) const | |
| { | |
| const auto &type = ::any::type(*this); | |
| if (type != ::any::type(*other)) | |
| return false; | |
| if (type == TYPEID(void)) | |
| return true; | |
| using value_type = value_of_t<iequality_comparable>; | |
| return value(*this) == ::any::any_static_cast<value_type>(*other); | |
| } | |
| }; | |
| ////////////////////////////////////////////////////////////////////////////////////////// | |
| // isemiregular | |
| template <class Base> | |
| struct isemiregular | |
| : interface<isemiregular, Base, extends<icopyable, iequality_comparable>> | |
| { | |
| using isemiregular::interface::interface; | |
| }; | |
| } // namespace any | |
| /////////////////////////////////////////////////////////////////////////////////////////////////////// | |
| template <class Base> | |
| struct ifoo : any::interface<ifoo, Base> | |
| { | |
| using ifoo::interface::interface; | |
| constexpr virtual void foo() | |
| { | |
| any::value(*this).foo(); | |
| } | |
| constexpr virtual void cfoo() const | |
| { | |
| any::value(*this).cfoo(); | |
| } | |
| }; | |
| template <class Base> | |
| struct ibar : any::interface<ibar, Base, any::extends<ifoo, any::icopyable>> | |
| { | |
| using ibar::interface::interface; | |
| constexpr virtual void bar() | |
| { | |
| any::value(*this).bar(); | |
| } | |
| }; | |
| template <class Base> | |
| struct ibaz : any::interface<ibaz, Base, any::extends<ibar>, 5 * sizeof(void *)> | |
| { | |
| using ibaz::interface::interface; | |
| constexpr ~ibaz() = default; | |
| constexpr virtual void baz() | |
| { | |
| any::value(*this).baz(); | |
| } | |
| }; | |
| struct foobar | |
| { | |
| constexpr void foo() | |
| { | |
| if (!std::is_constant_evaluated()) | |
| std::printf("foo override, value = %d\n", value); | |
| } | |
| constexpr void cfoo() const | |
| { | |
| if (!std::is_constant_evaluated()) | |
| std::printf("cfoo override, value = %d\n", value); | |
| } | |
| constexpr void bar() | |
| { | |
| if (!std::is_constant_evaluated()) | |
| std::printf("bar override, value = %d\n", value); | |
| } | |
| constexpr void baz() | |
| { | |
| if (!std::is_constant_evaluated()) | |
| std::printf("baz override, value = %d\n", value); | |
| } | |
| bool operator==(const foobar &other) const noexcept = default; | |
| int value = 42; | |
| }; | |
| static_assert( | |
| std::derived_from<any::iabstract<any::icopyable>, any::iabstract<any::imovable>>); | |
| static_assert(std::derived_from<any::iabstract<ibar>, any::iabstract<ifoo>>); | |
| static_assert(!std::derived_from<any::iabstract<ibar>, any::iabstract<any::icopyable>>); | |
| static_assert(any::extension_of<any::iabstract<ibar>, any::icopyable>); | |
| // Test the Diamond of Death inheritance problem: | |
| template <class Base> | |
| struct IFoo : any::interface<IFoo, Base, any::extends<any::icopyable>> | |
| { | |
| using IFoo::interface::interface; | |
| constexpr virtual void foo() | |
| { | |
| any::value(*this).foo(); | |
| } | |
| }; | |
| template <class Base> | |
| struct IBar : any::interface<IBar, Base, any::extends<any::icopyable>> | |
| { | |
| using IBar::interface::interface; | |
| constexpr virtual void bar() | |
| { | |
| any::value(*this).bar(); | |
| } | |
| }; | |
| template <class Base> | |
| struct IBaz : any::interface<IBaz, Base, any::extends<IFoo, IBar>> // inherits twice | |
| // from icopyable | |
| { | |
| using IBaz::interface::interface; | |
| constexpr virtual void baz() | |
| { | |
| any::value(*this).baz(); | |
| } | |
| }; | |
| static_assert(std::derived_from<any::iabstract<IBaz>, any::iabstract<IFoo>>); | |
| static_assert(std::derived_from<any::iabstract<IBaz>, any::iabstract<any::icopyable>>); | |
| void test_deadly_diamond_of_death() | |
| { | |
| any::any<IBaz> m(foobar{}); | |
| m.foo(); | |
| m.bar(); | |
| m.baz(); | |
| } | |
| static_assert(any::iabstract<ifoo>::buffer_size < any::iabstract<ibaz>::buffer_size); | |
| // test constant evaluation works | |
| consteval void test() | |
| { | |
| any::any<ibaz> m(foobar{}); | |
| [[maybe_unused]] auto x = any::any_static_cast<foobar>(m); | |
| x = any::any_cast<foobar>(m); | |
| m.foo(); | |
| [[maybe_unused]] auto n = m; | |
| [[maybe_unused]] auto p = any::caddressof(m); | |
| } | |
| int main() | |
| { | |
| test(); | |
| std::printf("sizeof void*: %d\n", (int)sizeof(void *)); | |
| std::printf("sizeof interface: %d\n", (int)sizeof(any::iabstract<ibaz>)); | |
| any::any<ibaz> m(foobar{}); | |
| ASSERT(m._in_situ()); | |
| ASSERT(any::type(m) == TYPEID(foobar)); | |
| m.foo(); | |
| m.bar(); | |
| m.baz(); | |
| any::any<ifoo> n = std::move(m); | |
| n.foo(); | |
| auto ptr = any::caddressof(m); | |
| any::_unconst(*ptr).foo(); | |
| // ptr->foo(); // does not compile because it is a const-correctness violation | |
| ptr->cfoo(); | |
| const auto ptr2 = any::addressof(m); | |
| ptr2->foo(); | |
| any::any_ptr<ifoo> pifoo = ptr2; | |
| m = *ptr; // assignment from type-erased references is supported | |
| any::any<any::isemiregular> a = 42; | |
| any::any<any::isemiregular> b = 42; | |
| any::any<any::isemiregular> c = 43; | |
| ASSERT(a == b); | |
| ASSERT(!(a != b)); | |
| ASSERT(!(a == c)); | |
| ASSERT(a != c); | |
| any::reset(b); | |
| ASSERT(!(a == b)); | |
| ASSERT(a != b); | |
| ASSERT(!(b == a)); | |
| ASSERT(b != a); | |
| any::any<any::iequality_comparable> x = a; | |
| ASSERT(x == x); | |
| ASSERT(x == a); | |
| ASSERT(a == x); | |
| a = 43; | |
| ASSERT(x != a); | |
| ASSERT(a != x); | |
| any::reset(a); | |
| ASSERT(b == a); | |
| auto z = any::caddressof(c); | |
| [[maybe_unused]] const int *p = &any::any_cast<int>(c); | |
| [[maybe_unused]] const int *q = any::any_cast<int>(z); | |
| ASSERT(any::any_cast<int>(z) == &any::any_cast<int>(c)); | |
| auto y = any::addressof(c); | |
| int *r = any::any_cast<int>(std::move(y)); | |
| ASSERT(r == &any::any_cast<int>(c)); | |
| z = y; // assign non-const ptr to const ptr | |
| z = &*y; | |
| ASSERT(y == z); | |
| test_deadly_diamond_of_death(); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment