include/unifex/any_unique.hpp (400 lines of code) (raw):
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* 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.
*/
#pragma once
#include <unifex/config.hpp>
#include <unifex/overload.hpp>
#include <unifex/tag_invoke.hpp>
#include <unifex/this.hpp>
#include <unifex/type_traits.hpp>
#include <unifex/std_concepts.hpp>
#include <memory>
#include <utility>
#include <unifex/detail/prologue.hpp>
namespace unifex {
namespace _any_unique {
template <typename CPO>
using base_cpo_t = _overload::base_cpo_t<CPO>;
template <typename CPO, typename T, bool NoExcept, typename Ret, typename... Args>
Ret _invoke(
base_cpo_t<CPO> cpo,
replace_this_with_void_ptr_t<Args>... args) noexcept(NoExcept) {
static_assert(!NoExcept || noexcept(extract_this<Args...>{}(args...)));
void* thisPointer = extract_this<Args...>{}(args...);
static_assert(!NoExcept || noexcept(Ret(((base_cpo_t<CPO>&&) cpo)(
replace_this<Args>::get(
(decltype(args)&&) args,
*static_cast<T*>(thisPointer))...))));
return ((base_cpo_t<CPO>&&) cpo)(
replace_this<Args>::get(
(decltype(args)&&) args,
*static_cast<T*>(thisPointer))...);
}
template <typename CPO, typename Sig = typename CPO::type_erased_signature_t>
struct vtable_entry;
template <typename CPO, typename Ret, typename... Args>
struct vtable_entry<CPO, Ret(Args...) noexcept> {
using fn_t =
Ret(base_cpo_t<CPO>, replace_this_with_void_ptr_t<Args>...) noexcept;
constexpr fn_t* get() const noexcept {
return fn_;
}
template <typename T>
static constexpr vtable_entry create() noexcept {
return vtable_entry{_any_unique::_invoke<CPO, T, true, Ret, Args...>};
}
private:
explicit constexpr vtable_entry(fn_t* fn) noexcept
: fn_(fn) {}
fn_t* fn_;
};
template <typename CPO, typename Ret, typename... Args>
struct vtable_entry<CPO, Ret(Args...)> {
using fn_t =
Ret(base_cpo_t<CPO>, replace_this_with_void_ptr_t<Args>...);
constexpr fn_t* get() const noexcept {
return fn_;
}
template <typename T>
static constexpr vtable_entry create() noexcept {
return vtable_entry{_any_unique::_invoke<CPO, T, false, Ret, Args...>};
}
private:
explicit constexpr vtable_entry(fn_t* fn) noexcept
: fn_(fn) {}
fn_t* fn_;
};
template <typename... CPOs>
struct vtable : private vtable_entry<CPOs>... {
template <typename T>
static constexpr vtable create() noexcept {
return vtable{vtable_entry<CPOs>::template create<T>()...};
}
template <typename CPO>
constexpr auto get() const noexcept -> typename vtable_entry<CPO>::fn_t* {
const vtable_entry<CPO>& entry = *this;
return entry.get();
}
private:
explicit constexpr vtable(vtable_entry<CPOs>... entries) noexcept
: vtable_entry<CPOs>{entries}... {}
};
template <typename... CPOs>
struct indirect_vtable_holder {
template <typename T>
static indirect_vtable_holder create() {
static constexpr vtable<CPOs...> v = vtable<CPOs...>::template create<T>();
return indirect_vtable_holder{v};
}
const vtable<CPOs...>& operator*() const noexcept {
return *vtable_;
}
const vtable<CPOs...>* operator->() const noexcept {
return vtable_;
}
private:
constexpr indirect_vtable_holder(const vtable<CPOs...>& vtable)
: vtable_(&vtable) {}
const vtable<CPOs...>* vtable_;
};
template <typename... CPOs>
struct inline_vtable_holder {
template <typename T>
static constexpr inline_vtable_holder create() {
return inline_vtable_holder{vtable<CPOs...>::template create<T>()};
}
const vtable<CPOs...>& operator*() const noexcept {
return vtable_;
}
const vtable<CPOs...>* operator->() const noexcept {
return &vtable_;
}
private:
constexpr inline_vtable_holder(const vtable<CPOs...>& vtable)
: vtable_(vtable) {}
vtable<CPOs...> vtable_;
};
template <typename... CPOs>
using vtable_holder = conditional_t<
(sizeof...(CPOs) <= 4),
inline_vtable_holder<CPOs...>,
indirect_vtable_holder<CPOs...>>;
template <
typename Derived,
typename CPO,
bool NoExcept,
typename Sig>
struct _with_type_erased_tag_invoke;
template <
typename Derived,
typename CPO,
bool NoExcept = false,
typename Sig = typename CPO::type_erased_signature_t>
using with_type_erased_tag_invoke =
typename _with_type_erased_tag_invoke<Derived, CPO, NoExcept, Sig>::type;
template <
typename Derived,
typename CPO,
bool NoExcept,
typename Ret,
typename... Args>
struct _with_type_erased_tag_invoke<
Derived,
CPO,
NoExcept,
Ret(Args...)> {
struct type {
friend Ret tag_invoke(base_cpo_t<CPO> cpo, replace_this_t<Args, Derived>... args)
noexcept(NoExcept) {
using cpo_t = base_cpo_t<CPO>;
static_assert(
!NoExcept || noexcept(extract_this<Args...>{}((decltype(args) &&) args...)));
auto&& t = extract_this<Args...>{}((decltype(args) &&) args...);
static_assert(!NoExcept || noexcept(get_object_address(t)));
void* objPtr = get_object_address(t);
static_assert(!NoExcept || noexcept(get_vtable(t)->template get<CPO>()));
auto* fnPtr = get_vtable(t)->template get<CPO>();
static_assert(!NoExcept || noexcept(fnPtr(
(cpo_t&&) cpo,
replace_this<Args>::get((decltype(args) &&) args, objPtr)...)));
return fnPtr(
(cpo_t&&) cpo,
replace_this<Args>::get((decltype(args) &&) args, objPtr)...);
}
};
};
template <
typename Derived,
typename CPO,
typename Ret,
typename... Args>
struct _with_type_erased_tag_invoke<
Derived,
CPO,
false,
Ret(Args...) noexcept>
: _with_type_erased_tag_invoke<Derived, CPO, true, Ret(Args...)> {
};
template <
typename Derived,
typename CPO,
bool NoExcept,
typename Sig>
struct _with_forwarding_tag_invoke;
template <
typename Derived,
typename CPO,
bool NoExcept = false,
typename Sig = typename CPO::type_erased_signature_t>
using with_forwarding_tag_invoke =
typename _with_forwarding_tag_invoke<Derived, CPO, NoExcept, Sig>::type;
template <
typename Derived,
typename CPO,
bool NoExcept,
typename Ret,
typename... Args>
struct _with_forwarding_tag_invoke<
Derived,
CPO,
NoExcept,
Ret(Args...)> {
struct type {
friend Ret tag_invoke(
base_cpo_t<CPO> cpo,
replace_this_t<Args, Derived>... args) noexcept(NoExcept) {
static_assert(!NoExcept || noexcept(extract_this<Args...>{}(args...)));
auto& wrapper = extract_this<Args...>{}(args...);
auto& wrapped = wrapper.value;
static_assert(
!NoExcept || noexcept(std::move(cpo)(
replace_this<Args>::get((Args &&) args, wrapped)...)));
return std::move(cpo)(replace_this<Args>::get((Args &&) args, wrapped)...);
}
};
};
template <
typename Derived,
typename CPO,
typename Ret,
typename... Args>
struct _with_forwarding_tag_invoke<
Derived,
CPO,
false,
Ret(Args...) noexcept>
: _with_forwarding_tag_invoke<Derived, CPO, true, Ret(Args...)> {
};
struct _deallocate_cpo {
using type_erased_signature_t = void(this_&) noexcept;
template <typename T>
UNIFEX_ALWAYS_INLINE
void operator()(T& obj) const noexcept {
if constexpr (tag_invocable<_deallocate_cpo, T&>) {
static_assert(noexcept(tag_invoke(_deallocate_cpo{}, obj)));
tag_invoke(_deallocate_cpo{}, obj);
} else {
delete std::addressof(obj);
}
}
};
template <typename Concrete, typename Allocator>
struct _concrete_impl {
struct base {
using allocator_type = typename std::allocator_traits<
Allocator>::template rebind_alloc<base>;
template <typename... Args>
explicit base(std::allocator_arg_t, allocator_type alloc, Args&&... args)
noexcept(std::is_nothrow_move_constructible_v<allocator_type> &&
std::is_nothrow_constructible_v<Concrete, Args...>)
: value((Args &&) args...)
, alloc(std::move(alloc)) {}
friend void tag_invoke(_deallocate_cpo, base& impl) noexcept {
allocator_type allocCopy = std::move(impl.alloc);
impl.~base();
std::allocator_traits<allocator_type>::deallocate(
allocCopy, &impl, 1);
}
UNIFEX_NO_UNIQUE_ADDRESS Concrete value;
UNIFEX_NO_UNIQUE_ADDRESS allocator_type alloc;
};
template <typename... CPOs>
struct impl {
struct type : base, private with_forwarding_tag_invoke<base, CPOs>... {
using base::base;
};
};
};
template <typename Concrete, typename Allocator, typename... CPOs>
using concrete_impl =
typename _concrete_impl<Concrete, Allocator>::template impl<CPOs...>::type;
template <typename... CPOs>
struct _byval {
class type;
};
template <typename... CPOs>
class _byval<CPOs...>::type
: private with_type_erased_tag_invoke<type, CPOs>... {
public:
template <typename Concrete, typename Allocator, typename... Args>
explicit type(
std::allocator_arg_t,
Allocator alloc,
std::in_place_type_t<Concrete>,
Args&&... args)
: vtable_(vtable_holder_t::template create<
concrete_impl<Concrete, Allocator, CPOs...>>()) {
using concrete_type = concrete_impl<Concrete, Allocator, CPOs...>;
using allocator_type = typename concrete_type::allocator_type;
using allocator_traits = std::allocator_traits<allocator_type>;
allocator_type typedAllocator{std::move(alloc)};
auto ptr = allocator_traits::allocate(typedAllocator, 1);
UNIFEX_TRY {
// TODO: Ideally we'd use allocator_traits::construct() here but
// that makes it difficult to provide consistent behaviour across
// std::allocator and std::pmr::polymorphic_allocator as the latter
// automatically injects the extra allocator_arg/alloc params which
// ends up duplicating them. But std::allocator doesn't do the same
// injection of the parameters.
::new ((void*)ptr)
concrete_type{std::allocator_arg, typedAllocator, (Args &&) args...};
} UNIFEX_CATCH (...) {
allocator_traits::deallocate(typedAllocator, ptr, 1);
UNIFEX_RETHROW();
}
impl_ = static_cast<void*>(ptr);
}
template(typename Concrete, typename Allocator)
(requires (!same_as<std::allocator_arg_t, std::decay_t<Concrete>>) AND
(!instance_of_v<std::in_place_type_t, std::decay_t<Concrete>>))
type(Concrete&& concrete, Allocator alloc)
: type(
std::allocator_arg,
std::move(alloc),
std::in_place_type<remove_cvref_t<Concrete>>,
(Concrete &&) concrete) {}
template <typename Concrete, typename... Args>
explicit type([[maybe_unused]] std::in_place_type_t<Concrete> tag, Args&&... args)
: impl_(new Concrete((Args&&) args...))
, vtable_(vtable_holder_t::template create<Concrete>()) {}
template(typename Concrete)
(requires (!same_as<type, remove_cvref_t<Concrete>>) AND
(!instance_of_v<std::in_place_type_t, Concrete>))
type(Concrete&& concrete)
: impl_(new auto((Concrete&&) concrete))
, vtable_(vtable_holder_t::template create<std::decay_t<Concrete>>()) {}
type(type&& other) noexcept
: impl_(std::exchange(other.impl_, nullptr))
, vtable_(other.vtable_) {}
UNIFEX_ALWAYS_INLINE ~type() {
unsafe_deallocate();
}
void swap(type& other) noexcept {
std::swap(vtable_, other.vtable_);
std::swap(impl_, other.impl_);
}
type& operator=(type other) noexcept {
swap(other);
return *this;
}
private:
using vtable_holder_t = vtable_holder<_deallocate_cpo, CPOs...>;
UNIFEX_ALWAYS_INLINE void unsafe_deallocate() noexcept {
// This leaves the any_unique in an invalid state.
if (nullptr != impl_) {
static_assert(noexcept(vtable_->template get<_deallocate_cpo>()));
auto* deallocateFn = vtable_->template get<_deallocate_cpo>();
static_assert(noexcept(deallocateFn(_deallocate_cpo{}, impl_)));
deallocateFn(_deallocate_cpo{}, impl_);
}
}
friend void swap(type& left, type& right) noexcept {
left.swap(right);
}
friend const vtable_holder_t& get_vtable(const type& self) noexcept {
return self.vtable_;
}
friend void* get_object_address(const type& self) noexcept {
return self.impl_;
}
void* impl_;
vtable_holder_t vtable_;
};
template <typename... CPOs>
struct _byref {
class type;
};
template <typename... CPOs>
class _byref<CPOs...>::type
: private with_type_erased_tag_invoke<type, CPOs>... {
public:
template (typename Concrete)
(requires (!same_as<Concrete const, type const>))
/*implicit*/ type(Concrete& impl)
: vtable_(vtable_holder_t::template create<Concrete>())
, impl_((void*) std::addressof(impl)) {}
void swap(type& other) noexcept {
std::swap(vtable_, other.vtable_);
std::swap(impl_, other.impl_);
}
// Two any_ref's compare equal IFF they refer to the same object (shallow).
friend bool operator==(type const& left, type const& right) noexcept {
return left.impl_ == right.impl_;
}
friend bool operator!=(type const& left, type const& right) noexcept {
return !(left == right);
}
private:
using vtable_holder_t = vtable_holder<CPOs...>;
friend void swap(type& left, type& right) noexcept {
left.swap(right);
}
friend const vtable_holder_t& get_vtable(const type& self) noexcept {
return self.vtable_;
}
friend void* get_object_address(const type& self) noexcept {
return self.impl_;
}
vtable_holder_t vtable_;
void* impl_;
};
} // namespace _any_unique
template <typename... CPOs>
using any_unique = typename _any_unique::_byval<CPOs...>::type;
template <auto&... CPOs>
using any_unique_t = any_unique<tag_t<CPOs>...>;
template <typename... CPOs>
using any_ref = typename _any_unique::_byref<CPOs...>::type;
template <auto&... CPOs>
using any_ref_t = any_ref<tag_t<CPOs>...>;
} // namespace unifex
#include <unifex/detail/epilogue.hpp>