blob: 190bda40f37cd75bc5a6222221a91550c2cec094 [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef LIB_FIT_INCLUDE_LIB_FIT_INLINE_ANY_INTERNAL_H_
#define LIB_FIT_INCLUDE_LIB_FIT_INLINE_ANY_INTERNAL_H_
#include <lib/stdcompat/type_traits.h>
#include <lib/stdcompat/utility.h>
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <functional>
#include <type_traits>
#include <utility>
#include "traits.h"
#include "utility_internal.h"
namespace fit {
namespace internal {
enum class inline_any_is_pinned : bool { no = false, yes = true };
template <typename I, size_t Reserve, size_t Align, inline_any_is_pinned Pinned>
class inline_any_impl {
static constexpr size_t storage_size = std::max(sizeof(I), Reserve);
static constexpr size_t storage_alignment = std::max(alignof(I), Align);
using storage = std::aligned_storage_t<storage_size, storage_alignment>;
template <typename... Conditions>
using requires_conditions = ::fit::internal::requires_conditions<Conditions...>;
template <typename... Conditions>
using assignment_requires =
::fit::internal::assignment_requires_conditions<inline_any_impl&, Conditions...>;
template <typename T>
using not_self_type = ::fit::internal::not_same_type<inline_any_impl, T>;
template <typename T>
using fits = cpp17::bool_constant<sizeof(cpp20::remove_cvref_t<T>) <= storage_size &&
alignof(cpp20::remove_cvref_t<T>) <= storage_alignment>;
template <typename T>
using is_compatible =
cpp17::conjunction<std::is_base_of<I, cpp20::remove_cvref_t<T>>, not_self_type<T>, fits<T>>;
public:
// Constructs an empty container.
inline_any_impl() = default;
~inline_any_impl() { reset(); }
template <typename T, requires_conditions<is_compatible<T>> = 0>
explicit inline_any_impl(T&& value) : op_{get_op_fn<T>()} {
new (&storage_) cpp20::remove_cvref_t<T>(std::forward<T>(value));
}
template <typename T, typename... Args,
requires_conditions<std::is_constructible<T, Args...>, is_compatible<T>> = 0>
explicit inline_any_impl(cpp17::in_place_type_t<T>, Args&&... args) : op_{get_op_fn<T>()} {
new (&storage_) cpp20::remove_cvref_t<T>(std::forward<Args>(args)...);
}
inline_any_impl(const inline_any_impl& other) : op_{other.op_} {
op_(opcode::copy_construct, &storage_, const_cast<storage*>(&other.storage_));
}
inline_any_impl(inline_any_impl&& other) noexcept : op_{other.op_} {
op_(opcode::move_construct, &storage_, &other.storage_);
other.reset();
}
template <typename T>
// |assignment_requires| becomes |inline_any_impl&| when the condition is satisfied.
// NOLINTNEXTLINE: clang-tidy doesn't understand |assignment_requires|.
assignment_requires<is_compatible<T>> operator=(T&& value) {
if (op_ == get_op_fn<T>()) {
reference<T>(&storage_) = std::forward<T>(value);
} else {
op_(opcode::destroy, &storage_, nullptr);
op_ = get_op_fn<T>();
new (&storage_) cpp20::remove_cvref_t<T>(std::forward<T>(value));
}
return *this;
}
inline_any_impl& operator=(const inline_any_impl& other) {
if (this != &other) {
if (op_ == other.op_) {
op_(opcode::copy, &storage_, const_cast<storage*>(&other.storage_));
} else {
op_(opcode::destroy, &storage_, nullptr);
op_ = other.op_;
op_(opcode::copy_construct, &storage_, const_cast<storage*>(&other.storage_));
}
}
return *this;
}
inline_any_impl& operator=(inline_any_impl&& other) noexcept {
if (this != &other) {
if (op_ == other.op_) {
op_(opcode::move, &storage_, &other.storage_);
} else {
op_(opcode::destroy, &storage_, nullptr);
op_ = other.op_;
op_(opcode::move_construct, &storage_, &other.storage_);
}
other.reset();
}
return *this;
}
// Initializes the container with an object of type |T|.
//
// Example:
//
// fit::inline_any<Animal> animal;
// animal.emplace<Dog>(args_to_dog_constructor);
//
template <typename T, typename... Args, requires_conditions<is_compatible<T>> = 0>
T& emplace(Args&&... args) {
reset();
op_ = get_op_fn<T>();
new (&storage_) cpp20::remove_cvref_t<T>(std::forward<Args>(args)...);
return as<T>();
}
// Resets the container back to an empty state.
void reset() {
op_(opcode::destroy, &storage_, nullptr);
op_ = default_op_fn;
}
// Whether the container contains an object.
bool has_value() const { return op_ != default_op_fn; }
// Whether the stored object type is |T|.
template <typename T, requires_conditions<is_compatible<T>> = 0>
bool is() const {
return op_ == get_op_fn<T>();
}
I* operator->() {
if (op_ != default_op_fn) {
return op_(opcode::get_interface, &storage_, nullptr);
}
__builtin_abort();
}
const I* operator->() const {
if (op_ != default_op_fn) {
return op_(opcode::get_interface, const_cast<storage*>(&storage_), nullptr);
}
__builtin_abort();
}
// Asserts that the stored object type is |T|, then access it.
template <typename T, requires_conditions<is_compatible<T>> = 0>
T& as() {
if (op_ == get_op_fn<T>()) {
return reference<T>(&storage_);
}
__builtin_abort();
}
template <typename T, requires_conditions<is_compatible<T>> = 0>
const T& as() const {
if (op_ == get_op_fn<T>()) {
return const_reference<T>(&storage_);
}
__builtin_abort();
}
// Asserts that an object is contained, then invokes |visitor| with a reference.
template <typename Callable, requires_conditions<cpp17::is_invocable<Callable, I&>> = 0>
decltype(auto) visit(Callable&& visitor) {
if (op_ != default_op_fn) {
return std::forward<Callable>(visitor)(*op_(opcode::get_interface, &storage_, nullptr));
}
__builtin_abort();
}
template <typename Callable, requires_conditions<cpp17::is_invocable<Callable, const I*>> = 0>
decltype(auto) visit(Callable&& visitor) const {
if (op_ != default_op_fn) {
return std::forward<Callable>(visitor)(op_(opcode::get_interface, &storage_, nullptr));
}
__builtin_abort();
}
// Asserts that an object is contained, then invokes |visitor| with a pointer.
template <typename Callable, requires_conditions<cpp17::is_invocable<Callable, I*>> = 0>
decltype(auto) visit(Callable&& visitor) {
if (op_ != default_op_fn) {
return std::forward<Callable>(visitor)(op_(opcode::get_interface, &storage_, nullptr));
}
__builtin_abort();
}
template <typename Callable, requires_conditions<cpp17::is_invocable<Callable, const I&>> = 0>
decltype(auto) visit(Callable&& visitor) const {
if (op_ != default_op_fn) {
return std::forward<Callable>(visitor)(*op_(opcode::get_interface, &storage_, nullptr));
}
__builtin_abort();
}
// Asserts that the stored object type is |T|, then invokes |visitor| with a reference.
template <typename T, typename Callable,
requires_conditions<is_compatible<T>, cpp17::is_invocable<Callable, T&>> = 0>
decltype(auto) visit_as(Callable&& visitor) {
if (op_ == get_op_fn<T>()) {
return std::forward<Callable>(visitor)(reference<T>(&storage_));
}
__builtin_abort();
}
template <typename T, typename Callable,
requires_conditions<is_compatible<T>, cpp17::is_invocable<Callable, const T&>> = 0>
decltype(auto) visit_as(Callable&& visitor) const {
if (op_ == get_op_fn<T>()) {
return std::forward<Callable>(visitor)(const_reference<T>(&storage_));
}
__builtin_abort();
}
// Asserts that the stored object type is |T|, then invokes |visitor| with a pointer.
template <typename T, typename Callable,
requires_conditions<is_compatible<T>, cpp17::is_invocable<Callable, T*>> = 0>
decltype(auto) visit_as(Callable&& visitor) {
if (op_ == get_op_fn<T>()) {
return std::forward<Callable>(visitor)(pointer<T>(&storage_));
}
__builtin_abort();
}
template <typename T, typename Callable,
requires_conditions<is_compatible<T>, cpp17::is_invocable<Callable, const T*>> = 0>
decltype(auto) visit_as(Callable&& visitor) const {
if (op_ == get_op_fn<T>()) {
return std::forward<Callable>(visitor)(const_pointer<T>(&storage_));
}
__builtin_abort();
}
private:
enum class opcode {
get_interface,
destroy,
// The following are only used when |!Pinned|.
copy_construct,
copy,
move_construct,
move,
};
using op_fn = I* (*)(opcode op, storage* self, storage* other);
static I* default_op_fn(opcode, storage*, storage*) { return nullptr; }
template <typename T>
static cpp20::remove_cvref_t<T>* pointer(storage* storage) {
return reinterpret_cast<cpp20::remove_cvref_t<T>*>(storage);
}
template <typename T>
static const cpp20::remove_cvref_t<T>* const_pointer(const storage* storage) {
return reinterpret_cast<const cpp20::remove_cvref_t<T>*>(storage);
}
template <typename T>
static cpp20::remove_cvref_t<T>& reference(storage* storage) {
return *pointer<T>(storage);
}
template <typename T>
static const cpp20::remove_cvref_t<T>& const_reference(const storage* storage) {
return *const_pointer<T>(storage);
}
template <typename T, inline_any_is_pinned pinned>
struct type_op_fn;
// Pinned.
template <typename T>
struct type_op_fn<T, inline_any_is_pinned::yes> {
template <typename U = T>
static I* fn(opcode op, storage* self, storage* other) {
switch (op) {
case opcode::get_interface:
return static_cast<I*>(pointer<T>(self));
case opcode::destroy:
pointer<T>(self)->T::~T();
return nullptr;
default:
__builtin_abort();
}
}
};
// Not pinned.
template <typename T>
struct type_op_fn<T, inline_any_is_pinned::no> {
template <typename U = T,
requires_conditions<std::is_move_constructible<T>, std::is_copy_constructible<T>> = 0>
static I* fn(opcode op, storage* self, storage* other) {
switch (op) {
case opcode::get_interface:
return static_cast<I*>(pointer<T>(self));
case opcode::destroy:
pointer<T>(self)->T::~T();
return nullptr;
case opcode::copy_construct:
new (self) T(const_reference<T>(other));
return nullptr;
case opcode::copy:
reference<T>(self) = const_reference<T>(other);
return nullptr;
case opcode::move_construct:
new (self) T(std::move(reference<T>(other)));
return nullptr;
case opcode::move:
reference<T>(self) = std::move(reference<T>(other));
return nullptr;
default:
__builtin_abort();
}
}
};
template <typename T>
static op_fn get_op_fn() {
return type_op_fn<cpp20::remove_cvref_t<T>, Pinned>::fn;
}
storage storage_;
op_fn op_{default_op_fn};
};
} // namespace internal
} // namespace fit
#endif // LIB_FIT_INCLUDE_LIB_FIT_INLINE_ANY_INTERNAL_H_