/* * Copyright (c) Facebook, Inc. and its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * 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. */ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace unifex { template < std::size_t InlineSize, std::size_t InlineAlignment, bool RequireNoexceptMove, typename DefaultAllocator, typename... CPOs> struct _any_object { // WORKAROUND: Define these static-constexpr members here instead of in the // nested // '::type' class below to work around an issue under MSVC where usages of // these variables in requires-clauses in 'type' constructors complains // about use of uninitialized variables causing these expressions to not be // constexpr. // Pad size/alignment out to allow storage of at least a pointer. static constexpr std::size_t padded_alignment = InlineAlignment < alignof(void*) ? alignof(void*) : InlineAlignment; static constexpr std::size_t padded_size = InlineSize < sizeof(void*) ? sizeof(void*) : InlineSize; // If it doesn't have a nothrow move-constructor and we need it to have a // nothrow move-constructor then we fall back to heap-allocating the object // and then we just move ownership of the pointer instead of moving the // underlying object. template static constexpr bool can_be_stored_inplace_v = (sizeof(T) <= padded_size && alignof(T) <= padded_alignment) && (!RequireNoexceptMove || std::is_nothrow_move_constructible_v); template static constexpr bool can_be_type_erased_v = unifex::detail:: supports_type_erased_cpos_v; struct invalid_obj : private detail::with_abort_tag_invoke... {}; static_assert(can_be_type_erased_v); class type; }; template inline constexpr bool _is_in_place_type = false; template inline constexpr bool _is_in_place_type> = true; template inline constexpr bool _is_any_object_tag_argument = _is_in_place_type || std::is_same_v; template < std::size_t InlineSize, std::size_t InlineAlignment, bool RequireNoexceptMove, typename DefaultAllocator, typename... CPOs> class _any_object< InlineSize, InlineAlignment, RequireNoexceptMove, DefaultAllocator, CPOs...>::type : private with_type_erased_tag_invoke... { using vtable_holder_t = detail::indirect_vtable_holder< detail::_destroy_cpo, detail::_move_construct_cpo, CPOs...>; public: template(typename T) // (requires(!same_as, type>) AND // (!_is_any_object_tag_argument>) AND // constructible_from, T> AND // _any_object::can_be_type_erased_v> AND // (_any_object::can_be_stored_inplace_v> || // default_constructible)) // /*implicit*/ type(T&& object) noexcept( _any_object::can_be_stored_inplace_v>&& std::is_nothrow_constructible_v, T>) : type(std::in_place_type>, static_cast(object)) {} template(typename T, typename Allocator) // (requires _any_object::can_be_type_erased_v> AND // constructible_from, T>) // explicit type( std::allocator_arg_t, Allocator allocator, T&& value) noexcept(_any_object::can_be_stored_inplace_v>&& std::is_nothrow_constructible_v< remove_cvref_t, T>) : type( std::allocator_arg, std::move(allocator), std::in_place_type>, static_cast(value)) {} template(typename T, typename... Args) // (requires constructible_from AND _any_object::can_be_stored_inplace_v) // explicit type(std::in_place_type_t, Args&&... args) noexcept( std::is_nothrow_constructible_v) : vtable_(vtable_holder_t::template create()) { ::new (static_cast(&storage_)) T(static_cast(args)...); } template(typename T, typename... Args) // (requires constructible_from AND // _any_object::can_be_type_erased_v AND // (!_any_object::can_be_stored_inplace_v) AND // default_constructible) // explicit type(std::in_place_type_t, Args&&... args) : type( std::allocator_arg, DefaultAllocator(), std::in_place_type, static_cast(args)...) {} template(typename T, typename Allocator, typename... Args) // (requires _any_object::can_be_type_erased_v AND // _any_object::can_be_stored_inplace_v) // explicit type( std::allocator_arg_t, Allocator, std::in_place_type_t, Args&&... args) noexcept(std:: is_nothrow_constructible_v) : type(std::in_place_type, static_cast(args)...) {} template(typename T, typename Alloc, typename... Args) // (requires _any_object::can_be_type_erased_v AND // (!_any_object::can_be_stored_inplace_v)) // explicit type( std::allocator_arg_t, Alloc alloc, std::in_place_type_t, Args&&... args) // COMPILER: This should ideally be delegating to the constructor: // // type(std::in_place_type< // detail::any_heap_allocated_storage, // std::allocator_arg, std::move(alloc), std::in_place_type, // static_cast(args)...) // // But doing so causes an infinite recursion of template constructor // instantiations under MSVC 14.31.31103 (as well as other versions). // So instead we duplicate the body of the constructor this would end // up calling here to avoid the recursive instantiations. : vtable_(vtable_holder_t::template create< detail::any_heap_allocated_storage>()) { ::new (static_cast(&storage_)) detail::any_heap_allocated_storage( std::allocator_arg, std::move(alloc), std::in_place_type, static_cast(args)...); } type(const type&) = delete; type(type&& other) noexcept(RequireNoexceptMove) : vtable_(other.vtable_) { auto* moveConstruct = vtable_->template get< detail::_move_construct_cpo>(); moveConstruct( detail::_move_construct_cpo{}, &storage_, &other.storage_); } ~type() { auto* destroy = vtable_->template get(); destroy(detail::_destroy_cpo{}, &storage_); } // Assign from another type-erased instance. type& operator=(type&& other) noexcept(RequireNoexceptMove) { if (std::addressof(other) != this) { auto* destroy = vtable_->template get(); destroy(detail::_destroy_cpo{}, &storage_); // Assign the vtable for an empty, trivially constructible/destructible // object. Just in case the move-construction below throws. So we at // least leave the current object in a valid state. if constexpr (!RequireNoexceptMove) { vtable_ = vtable_holder_t::template create(); } auto* moveConstruct = other.vtable_->template get< detail::_move_construct_cpo>(); moveConstruct( detail::_move_construct_cpo{}, &storage_, &other.storage_); // Now that we've successfully constructed a new object we can overwrite // the 'invalid_obj' vtable with the one for the new object. vtable_ = other.vtable_; // Note that we are leaving the source object in a constructed state // rather than unconditionally destroying it here as doing so would mean // we'd need to assign the 'invalid_obj' vtable to the source object and // then the source obj would incur an extra indirect call to the // `invalid_obj` vtable destructor entry. } return *this; } template(typename T) // (requires _any_object::can_be_type_erased_v AND // constructible_from, T> AND // _any_object::can_be_stored_inplace_v>) // type& operator=(T&& value) noexcept( std::is_nothrow_constructible_v, T>) { auto* destroy = vtable_->template get(); destroy(detail::_destroy_cpo{}, &storage_); using value_type = remove_cvref_t; if (!std::is_nothrow_constructible_v) { vtable_ = vtable_holder_t::template create(); } ::new (static_cast(&storage_)) value_type(static_cast(value)); vtable_ = vtable_holder_t::template create(); return *this; } template(typename T) // (requires _any_object::can_be_type_erased_v AND // constructible_from, T> AND // default_constructible AND // (!_any_object::can_be_stored_inplace_v>)) // type& operator=(T&& value) { auto* destroy = vtable_->template get(); destroy(detail::_destroy_cpo{}, &storage_); vtable_ = vtable_holder_t::template create(); using value_type = detail::any_heap_allocated_storage< remove_cvref_t, DefaultAllocator, CPOs...>; ::new (static_cast(&storage_)) value_type( std::allocator_arg, DefaultAllocator{}, std::in_place_type>, static_cast(value)); vtable_ = vtable_holder_t::template create(); return *this; } private: friend const vtable_holder_t& get_vtable(const type& self) noexcept { return self.vtable_; } friend void* get_object_address(const type& self) noexcept { return const_cast(static_cast(&self.storage_)); } vtable_holder_t vtable_; alignas(padded_alignment) std::byte storage_[padded_size]; }; template < std::size_t InlineSize, std::size_t InlineAlignment, bool RequireNoexceptMove, typename DefaultAllocator, typename... CPOs> using basic_any_object = typename _any_object< InlineSize, InlineAlignment, RequireNoexceptMove, DefaultAllocator, CPOs...>::type; template < std::size_t InlineSize, std::size_t InlineAlignment, bool RequireNoexceptMove, typename DefaultAllocator, auto&... CPOs> using basic_any_object_t = basic_any_object< InlineSize, InlineAlignment, RequireNoexceptMove, DefaultAllocator, unifex::tag_t...>; // Simpler version that chooses some appropriate defaults for you. template using any_object = basic_any_object< 3 * sizeof(void*), alignof(void*), true, std::allocator, CPOs...>; template using any_object_t = any_object...>; } // namespace unifex #include