blob: 8cffd02599c327b31d3155c11c3a95cfdc6b27e9 [file] [log] [blame]
// Copyright 2021 The Pigweed Authors
//
// 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
//
// https://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 <cstddef>
#include <new>
#include <utility>
#include "pw_assert/assert.h"
#include "pw_function/config.h"
#include "pw_preprocessor/compiler.h"
namespace pw::function_internal {
template <typename T, typename Comparison = bool>
struct NullEq {
static constexpr bool Test(const T&) { return false; }
};
// Partial specialization for values of T comparable to nullptr.
template <typename T>
struct NullEq<T, decltype(std::declval<T>() == nullptr)> {
// This is intended to be used for comparing function pointers to nullptr, but
// the specialization also matches Ts that implicitly convert to a function
// pointer, such as function types. The compiler may then complain that the
// comparison is false, as the address is known at compile time and cannot be
// nullptr. Silence this warning. (The compiler will optimize out the
// comparison.)
PW_MODIFY_DIAGNOSTICS_PUSH();
PW_MODIFY_DIAGNOSTIC(ignored, "-Waddress");
static constexpr bool Test(const T& v) { return v == nullptr; }
PW_MODIFY_DIAGNOSTICS_POP();
};
// Tests whether a value is considered to be null.
template <typename T>
static constexpr bool IsNull(const T& v) {
return NullEq<T>::Test(v);
}
// FunctionTarget is an interface for storing a callable object and providing a
// way to invoke it. The GenericFunctionTarget expresses the interface common to
// all pw::Function instantiations. The derived FunctionTarget class adds the
// call operator, which is templated on the function arguments and return type.
class GenericFunctionTarget {
public:
constexpr GenericFunctionTarget() = default;
GenericFunctionTarget(const GenericFunctionTarget&) = delete;
GenericFunctionTarget(GenericFunctionTarget&&) = delete;
GenericFunctionTarget& operator=(const GenericFunctionTarget&) = delete;
GenericFunctionTarget& operator=(GenericFunctionTarget&&) = delete;
virtual void Destroy() {}
// Only returns true for NullFunctionTarget.
virtual bool IsNull() const { return false; }
// Move initialize the function target to a provided location.
virtual void MoveInitializeTo(void* ptr) = 0;
protected:
~GenericFunctionTarget() = default; // The destructor is never called.
};
// FunctionTarget is an interface for storing a callable object and providing a
// way to invoke it.
template <typename Return, typename... Args>
class FunctionTarget : public GenericFunctionTarget {
public:
constexpr FunctionTarget() = default;
// Invoke the callable stored by the function target.
virtual Return operator()(Args... args) const = 0;
protected:
~FunctionTarget() = default; // The destructor is never called.
};
// A function target that does not store any callable. Attempting to invoke it
// results in a crash.
class NullFunctionTarget final : public FunctionTarget<void> {
public:
constexpr NullFunctionTarget() = default;
NullFunctionTarget(const NullFunctionTarget&) = delete;
NullFunctionTarget(NullFunctionTarget&&) = delete;
NullFunctionTarget& operator=(const NullFunctionTarget&) = delete;
NullFunctionTarget& operator=(NullFunctionTarget&&) = delete;
bool IsNull() const final { return true; }
void operator()() const final { PW_ASSERT(false); }
void MoveInitializeTo(void* ptr) final { new (ptr) NullFunctionTarget(); }
};
// Function target that stores a callable as a member within the class.
template <typename Callable, typename Return, typename... Args>
class InlineFunctionTarget final : public FunctionTarget<Return, Args...> {
public:
explicit InlineFunctionTarget(Callable&& callable)
: callable_(std::move(callable)) {}
void Destroy() final { callable_.~Callable(); }
InlineFunctionTarget(const InlineFunctionTarget&) = delete;
InlineFunctionTarget& operator=(const InlineFunctionTarget&) = delete;
InlineFunctionTarget(InlineFunctionTarget&& other)
: callable_(std::move(other.callable_)) {}
InlineFunctionTarget& operator=(InlineFunctionTarget&&) = default;
Return operator()(Args... args) const final {
return callable_(std::forward<Args>(args)...);
}
void MoveInitializeTo(void* ptr) final {
new (ptr) InlineFunctionTarget(std::move(*this));
}
private:
// This must be mutable to support custom objects that implement operator() in
// a non-const way.
mutable Callable callable_;
};
// Function target which stores a callable at a provided location in memory.
// The creating context must ensure that the region is properly sized and
// aligned for the callable.
template <typename Callable, typename Return, typename... Args>
class MemoryFunctionTarget final : public FunctionTarget<Return, Args...> {
public:
MemoryFunctionTarget(void* address, Callable&& callable) : address_(address) {
new (address_) Callable(std::move(callable));
}
void Destroy() final {
// Multiple MemoryFunctionTargets may have referred to the same callable
// (due to moves), but only one can have a valid pointer to it. The owner is
// responsible for destructing the callable.
if (address_ != nullptr) {
callable().~Callable();
}
}
MemoryFunctionTarget(const MemoryFunctionTarget&) = delete;
MemoryFunctionTarget& operator=(const MemoryFunctionTarget&) = delete;
// Transfer the pointer to the initialized callable to this object without
// reinitializing the callable, clearing the address from the other.
MemoryFunctionTarget(MemoryFunctionTarget&& other)
: address_(other.address_) {
other.address_ = nullptr;
}
MemoryFunctionTarget& operator=(MemoryFunctionTarget&&) = default;
Return operator()(Args... args) const final { return callable()(args...); }
void MoveInitializeTo(void* ptr) final {
new (ptr) MemoryFunctionTarget(std::move(*this));
}
private:
Callable& callable() {
return *std::launder(reinterpret_cast<Callable*>(address_));
}
const Callable& callable() const {
return *std::launder(reinterpret_cast<const Callable*>(address_));
}
void* address_;
};
template <size_t kSizeBytes>
using FunctionStorage =
std::aligned_storage_t<kSizeBytes, alignof(std::max_align_t)>;
// A FunctionTargetHolder stores an instance of a FunctionTarget implementation.
//
// The concrete implementation is initialized in an internal buffer by calling
// one of the initialization functions. After initialization, all
// implementations are accessed through the virtual FunctionTarget base.
template <size_t kSizeBytes, typename Return, typename... Args>
class FunctionTargetHolder {
public:
constexpr FunctionTargetHolder() : null_function_ {}
{}
FunctionTargetHolder(const FunctionTargetHolder&) = delete;
FunctionTargetHolder(FunctionTargetHolder&&) = delete;
FunctionTargetHolder& operator=(const FunctionTargetHolder&) = delete;
FunctionTargetHolder& operator=(FunctionTargetHolder&&) = delete;
constexpr void InitializeNullTarget() {
static_assert(sizeof(NullFunctionTarget) <= kSizeBytes,
"NullFunctionTarget must fit within FunctionTargetHolder");
new (&null_function_) NullFunctionTarget;
}
// Initializes an InlineFunctionTarget with the callable, failing if it is too
// large.
template <typename Callable>
void InitializeInlineTarget(Callable callable) {
using InlineFunctionTarget =
InlineFunctionTarget<Callable, Return, Args...>;
static_assert(sizeof(InlineFunctionTarget) <= kSizeBytes,
"Inline callable must fit within FunctionTargetHolder");
new (&bits_) InlineFunctionTarget(std::move(callable));
}
// Initializes a MemoryTarget that stores the callable at the provided
// location.
template <typename Callable>
void InitializeMemoryTarget(Callable callable, void* storage) {
using MemoryFunctionTarget =
MemoryFunctionTarget<Callable, Return, Args...>;
static_assert(sizeof(MemoryFunctionTarget) <= kSizeBytes,
"MemoryFunctionTarget must fit within FunctionTargetHolder");
new (&bits_) MemoryFunctionTarget(storage, std::move(callable));
}
void DestructTarget() { target().Destroy(); }
// Initializes the function target within this callable from another target
// holder's function target.
void MoveInitializeTargetFrom(FunctionTargetHolder& other) {
other.target().MoveInitializeTo(&bits_);
}
// The stored implementation is accessed by punning to the virtual base class.
using Target = FunctionTarget<Return, Args...>;
Target& target() { return *std::launder(reinterpret_cast<Target*>(&bits_)); }
const Target& target() const {
return *std::launder(reinterpret_cast<const Target*>(&bits_));
}
private:
// Storage for an implementation of the FunctionTarget interface. Make this a
// union with NullFunctionTarget so that the constexpr constructor can
// initialize null_function_ directly.
union {
FunctionStorage<kSizeBytes> bits_;
NullFunctionTarget null_function_;
};
};
} // namespace pw::function_internal