blob: a7c3670b1dd90459327520b2641a7376ccc9c270 [file] [log] [blame]
// Copyright 2017 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_FUNCTION_INTERNAL_H_
#define LIB_FIT_FUNCTION_INTERNAL_H_
#include <stddef.h>
#include <stdlib.h>
#include <memory>
#include "nullable.h"
#include <new>
#include <type_traits>
#include <utility>
namespace fit {
namespace internal {
template <typename Result, typename... Args>
struct target_ops final {
const void* (*target_type_id)(void* bits, const void* impl_ops);
void* (*get)(void* bits);
Result (*invoke)(void* bits, Args... args);
void (*move)(void* from_bits, void* to_bits);
void (*destroy)(void* bits);
};
template <typename Callable, bool is_inline, bool is_shared, typename Result, typename... Args>
struct target;
inline const void* unshared_target_type_id(void* bits, const void* impl_ops) { return impl_ops; }
// vtable for nullptr (empty target function)
template <typename Result, typename... Args>
struct target<decltype(nullptr),
/*is_inline=*/true, /*is_shared=*/false, Result, Args...>
final {
static Result invoke(void* bits, Args... args) { __builtin_abort(); }
static const target_ops<Result, Args...> ops;
};
inline void* null_target_get(void* bits) { return nullptr; }
inline void null_target_move(void* from_bits, void* to_bits) {}
inline void null_target_destroy(void* bits) {}
template <typename Result, typename... Args>
constexpr target_ops<Result, Args...> target<decltype(nullptr),
/*is_inline=*/true,
/*is_shared=*/false, Result, Args...>::ops = {
&unshared_target_type_id, &null_target_get, &target::invoke, &null_target_move,
&null_target_destroy};
// vtable for inline target function
template <typename Callable, typename Result, typename... Args>
struct target<Callable,
/*is_inline=*/true, /*is_shared=*/false, Result, Args...>
final {
static void initialize(void* bits, Callable&& target) { new (bits) Callable(std::move(target)); }
static Result invoke(void* bits, Args... args) {
auto& target = *static_cast<Callable*>(bits);
return target(std::forward<Args>(args)...);
}
static void move(void* from_bits, void* to_bits) {
auto& from_target = *static_cast<Callable*>(from_bits);
new (to_bits) Callable(std::move(from_target));
from_target.~Callable();
}
static void destroy(void* bits) {
auto& target = *static_cast<Callable*>(bits);
target.~Callable();
}
static const target_ops<Result, Args...> ops;
};
inline void* inline_target_get(void* bits) { return bits; }
template <typename Callable, typename Result, typename... Args>
constexpr target_ops<Result, Args...> target<Callable,
/*is_inline=*/true,
/*is_shared=*/false, Result, Args...>::ops = {
&unshared_target_type_id, &inline_target_get, &target::invoke, &target::move, &target::destroy};
// vtable for pointer to target function
template <typename Callable, typename Result, typename... Args>
struct target<Callable,
/*is_inline=*/false, /*is_shared=*/false, Result, Args...>
final {
static void initialize(void* bits, Callable&& target) {
auto ptr = static_cast<Callable**>(bits);
*ptr = new Callable(std::move(target));
}
static Result invoke(void* bits, Args... args) {
auto& target = **static_cast<Callable**>(bits);
return target(std::forward<Args>(args)...);
}
static void move(void* from_bits, void* to_bits) {
auto from_ptr = static_cast<Callable**>(from_bits);
auto to_ptr = static_cast<Callable**>(to_bits);
*to_ptr = *from_ptr;
}
static void destroy(void* bits) {
auto ptr = static_cast<Callable**>(bits);
delete *ptr;
}
static const target_ops<Result, Args...> ops;
};
inline void* heap_target_get(void* bits) { return *static_cast<void**>(bits); }
template <typename Callable, typename Result, typename... Args>
constexpr target_ops<Result, Args...> target<Callable,
/*is_inline=*/false,
/*is_shared=*/false, Result, Args...>::ops = {
&unshared_target_type_id, &heap_target_get, &target::invoke, &target::move, &target::destroy};
// vtable for fit::function std::shared_ptr to target function
template <typename SharedFunction>
const void* get_target_type_id(const SharedFunction& function_or_callback) {
return function_or_callback.target_type_id();
}
// For this vtable,
// Callable by definition will be either a fit::function or fit::callback
template <typename SharedFunction, typename Result, typename... Args>
struct target<SharedFunction,
/*is_inline=*/false, /*is_shared=*/true, Result, Args...>
final {
static void initialize(void* bits, SharedFunction target) {
new (bits) std::shared_ptr<SharedFunction>(
std::move(std::make_shared<SharedFunction>(std::move(target))));
}
static void copy_shared_ptr(void* from_bits, void* to_bits) {
auto& from_shared_ptr = *static_cast<std::shared_ptr<SharedFunction>*>(from_bits);
new (to_bits) std::shared_ptr<SharedFunction>(from_shared_ptr);
}
static const void* target_type_id(void* bits, const void* impl_ops) {
auto& function_or_callback = **static_cast<std::shared_ptr<SharedFunction>*>(bits);
return ::fit::internal::get_target_type_id(function_or_callback);
}
static void* get(void* bits) {
auto& function_or_callback = **static_cast<std::shared_ptr<SharedFunction>*>(bits);
return function_or_callback.template target<SharedFunction>(
/*check=*/false); // void* will fail the check
}
static Result invoke(void* bits, Args... args) {
auto& function_or_callback = **static_cast<std::shared_ptr<SharedFunction>*>(bits);
return function_or_callback(std::forward<Args>(args)...);
}
static void move(void* from_bits, void* to_bits) {
auto from_shared_ptr = std::move(*static_cast<std::shared_ptr<SharedFunction>*>(from_bits));
new (to_bits) std::shared_ptr<SharedFunction>(std::move(from_shared_ptr));
}
static void destroy(void* bits) { static_cast<std::shared_ptr<SharedFunction>*>(bits)->reset(); }
static const target_ops<Result, Args...> ops;
};
template <typename SharedFunction, typename Result, typename... Args>
constexpr target_ops<Result, Args...> target<SharedFunction,
/*is_inline=*/false,
/*is_shared=*/true, Result, Args...>::ops = {
&target::target_type_id, &target::get, &target::invoke, &target::move, &target::destroy};
template <size_t inline_target_size, bool require_inline, typename Result, typename... Args>
class function_base;
// Function implementation details.
// See |fit::function| and |fit::callback| documentation for more information.
template <size_t inline_target_size, bool require_inline, typename Result, typename... Args>
class function_base<inline_target_size, require_inline, Result(Args...)> {
using ops_type = const target_ops<Result, Args...>*;
using storage_type = typename std::aligned_storage<(
inline_target_size >= sizeof(void*) ? inline_target_size : sizeof(void*))>::
type; // avoid including <algorithm> for max
template <typename Callable>
using target_type = target<Callable, (sizeof(Callable) <= sizeof(storage_type)),
/*is_shared=*/false, Result, Args...>;
template <typename SharedFunction>
using shared_target_type = target<SharedFunction,
/*is_inline=*/false,
/*is_shared=*/true, Result, Args...>;
using null_target_type = target_type<decltype(nullptr)>;
protected:
using result_type = Result;
function_base() { initialize_null_target(); }
function_base(decltype(nullptr)) { initialize_null_target(); }
function_base(Result (*target)(Args...)) { initialize_target(target); }
template <typename Callable,
typename = std::enable_if_t<std::is_convertible<
decltype(std::declval<Callable&>()(std::declval<Args>()...)), result_type>::value>>
function_base(Callable target) {
initialize_target(std::move(target));
}
function_base(function_base&& other) { move_target_from(std::move(other)); }
~function_base() { destroy_target(); }
// Returns true if the function has a non-empty target.
explicit operator bool() const { return ops_->get(&bits_) != nullptr; }
// Returns a pointer to the function's target.
// If |check| is true (the default), the function _may_ abort if the
// caller tries to assign the target to a varible of the wrong type. (This
// check is currently skipped for share()d objects.)
// Note the shared pointer vtable must set |check| to false to assign the
// target to |void*|.
template <typename Callable>
Callable* target(bool check = true) {
if (check)
check_target_type<Callable>();
return static_cast<Callable*>(ops_->get(&bits_));
}
// Returns a pointer to the function's target (const version).
// If |check| is true (the default), the function _may_ abort if the
// caller tries to assign the target to a varible of the wrong type. (This
// check is currently skipped for share()d objects.)
// Note the shared pointer vtable must set |check| to false to assign the
// target to |void*|.
template <typename Callable>
const Callable* target(bool check = true) const {
if (check)
check_target_type<Callable>();
return static_cast<Callable*>(ops_->get(&bits_));
}
// Used by the derived "impl" classes to implement share().
//
// The caller creates a new object of the same type as itself, and passes in
// the empty object. This function first checks if |this| is already shared,
// and if not, creates a new version of itself containing a
// |std::shared_ptr| to its original self, and updates |ops_| to the vtable
// for the shared version.
//
// Then it copies its |shared_ptr| to the |bits_| of the given |copy|,
// and assigns the same shared pointer vtable to the copy's |ops_|.
//
// The target itself is not copied; it is moved to the heap and its
// lifetime is extended until all references have been released.
//
// Note: This method is not supported on |fit::inline_function<>|
// because it may incur a heap allocation which is contrary to
// the stated purpose of |fit::inline_function<>|.
template <typename SharedFunction>
void share_with(SharedFunction& copy) {
static_assert(!require_inline, "Inline functions cannot be shared.");
if (ops_->get(&bits_) != nullptr) {
if (ops_ != &shared_target_type<SharedFunction>::ops) {
convert_to_shared_target<SharedFunction>();
}
copy_shared_target_to(copy);
}
}
// Used by derived "impl" classes to implement operator()().
// Invokes the function's target.
// Note that fit::callback will release the target immediately after
// invoke() (also affecting any share()d copies).
// Aborts if the function's target is empty.
Result invoke(Args... args) const { return ops_->invoke(&bits_, std::forward<Args>(args)...); }
// Used by derived "impl" classes to implement operator=().
// Assigns an empty target.
void assign(decltype(nullptr)) {
destroy_target();
initialize_null_target();
}
// Used by derived "impl" classes to implement operator=().
// Assigns the function's target.
// If target == nullptr, assigns an empty target.
template <typename Callable,
typename = std::enable_if_t<std::is_convertible<
decltype(std::declval<Callable&>()(std::declval<Args>()...)), result_type>::value>>
void assign(Callable target) {
destroy_target();
initialize_target(std::move(target));
}
// Used by derived "impl" classes to implement operator=().
// Assigns the function with a target moved from another function,
// leaving the other function with an empty target.
void assign(function_base&& other) {
destroy_target();
move_target_from(std::move(other));
}
void swap(function_base& other) {
if (&other == this)
return;
ops_type temp_ops = ops_;
storage_type temp_bits;
ops_->move(&bits_, &temp_bits);
ops_ = other.ops_;
other.ops_->move(&other.bits_, &bits_);
other.ops_ = temp_ops;
temp_ops->move(&temp_bits, &other.bits_);
}
// returns an opaque ID unique to the |Callable| type of the target.
// Used by check_target_type.
const void* target_type_id() const { return ops_->target_type_id(&bits_, ops_); }
// Deleted copy constructor and assign. |function_base| implementations are
// move-only.
function_base(const function_base& other) = delete;
function_base& operator=(const function_base& other) = delete;
// Move assignment must be provided by subclasses.
function_base& operator=(function_base&& other) = delete;
private:
// Implements the move operation, used by move construction and move
// assignment. Leaves other target initialized to null.
void move_target_from(function_base&& other) {
ops_ = other.ops_;
other.ops_->move(&other.bits_, &bits_);
other.initialize_null_target();
}
// fit::function and fit::callback are not directly copyable, but share()
// will create shared references to the original object. This method
// implements the copy operation for the |std::shared_ptr| wrapper.
template <typename SharedFunction>
void copy_shared_target_to(SharedFunction& copy) {
copy.destroy_target();
assert(ops_ == &shared_target_type<SharedFunction>::ops);
shared_target_type<SharedFunction>::copy_shared_ptr(&bits_, &copy.bits_);
copy.ops_ = ops_;
}
// assumes target is uninitialized
void initialize_null_target() { ops_ = &null_target_type::ops; }
// assumes target is uninitialized
template <typename Callable>
void initialize_target(Callable target) {
static_assert(std::alignment_of<Callable>::value <= std::alignment_of<storage_type>::value,
"Alignment of Callable must be <= alignment of max_align_t.");
static_assert(!require_inline || sizeof(Callable) <= inline_target_size,
"Callable too large to store inline as requested.");
if (is_null(target)) {
initialize_null_target();
} else {
ops_ = &target_type<Callable>::ops;
target_type<Callable>::initialize(&bits_, std::move(target));
}
}
// assumes target is uninitialized
template <typename SharedFunction>
void convert_to_shared_target() {
shared_target_type<SharedFunction>::initialize(&bits_,
std::move(*static_cast<SharedFunction*>(this)));
ops_ = &shared_target_type<SharedFunction>::ops;
}
// leaves target uninitialized
void destroy_target() { ops_->destroy(&bits_); }
// Called by target() if |check| is true.
// Checks the template parameter, usually inferred from the context of
// the call to target(), and aborts the program if it can determine that
// the Callable type is not compatible with the function's Result and Args.
template <typename Callable>
void check_target_type() const {
if (target_type<Callable>::ops.target_type_id(nullptr, &target_type<Callable>::ops) !=
target_type_id()) {
__builtin_abort();
}
}
ops_type ops_;
mutable storage_type bits_;
};
} // namespace internal
} // namespace fit
#endif // LIB_FIT_FUNCTION_INTERNAL_H_