blob: 255874992004bb99ace0bd350975953fc60d2430 [file] [edit]
// https://github.com/WG21-SG14/SG14/blob/master/SG14/inplace_function.h
// Doc: https://github.com/WG21-SG14/SG14/blob/master/Docs/Proposals/NonAllocatingStandardFunction.pdf
/*
* Boost Software License - Version 1.0 - August 17th, 2003
*
* Permission is hereby granted, free of charge, to any person or organization
* obtaining a copy of the software and accompanying documentation covered by
* this license (the "Software") to use, reproduce, display, distribute,
* execute, and transmit the Software, and to prepare derivative works of the
* Software, and to permit third-parties to whom the Software is furnished to
* do so, all subject to the following:
*
* The copyright notices in the Software and this entire statement, including
* the above license grant, this restriction and the following disclaimer,
* must be included in all copies of the Software, in whole or in part, and
* all derivative works of the Software, unless such copies or derivative
* works are solely in the form of machine-executable object code generated by
* a source language processor.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
* SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
* FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include <algorithm>
#include <cstddef>
#include <functional>
#include <memory>
#include <type_traits>
#include <utility>
#ifndef SG14_INPLACE_FUNCTION_THROW
#define SG14_INPLACE_FUNCTION_THROW(x) throw(x)
#endif
namespace stdext {
namespace inplace_function_detail {
static constexpr size_t InplaceFunctionDefaultCapacity = 32;
template <class T>
struct wrapper {
using type = T;
};
template <class R, class... Args>
struct vtable {
using storage_ptr_t = void*;
using invoke_ptr_t = R (*)(storage_ptr_t, Args&&...);
using process_ptr_t = void (*)(storage_ptr_t, storage_ptr_t);
using destructor_ptr_t = void (*)(storage_ptr_t);
const invoke_ptr_t invoke_ptr;
const process_ptr_t copy_ptr;
const process_ptr_t relocate_ptr;
const destructor_ptr_t destructor_ptr;
explicit constexpr vtable() noexcept
: invoke_ptr{[](storage_ptr_t, Args&&...) -> R {
// Todo: we have exceptions disabled, so calling "throw" is not an option.
// Returning R() is a hack, R must have a default constructor for this to works.
// As of writing, it is ok to do this.
/*SG14_INPLACE_FUNCTION_THROW(std::bad_function_call());*/
return R();
}},
copy_ptr{[](storage_ptr_t, storage_ptr_t) -> void {}},
relocate_ptr{[](storage_ptr_t, storage_ptr_t) -> void {}},
destructor_ptr{[](storage_ptr_t) -> void {}} {}
template <class C>
explicit constexpr vtable(wrapper<C>) noexcept
: invoke_ptr{[](storage_ptr_t storage_ptr, Args&&... args) -> R {
return (*static_cast<C*>(storage_ptr))(static_cast<Args&&>(args)...);
}},
copy_ptr{[](storage_ptr_t dst_ptr, storage_ptr_t src_ptr) -> void { ::new (dst_ptr) C{(*static_cast<C*>(src_ptr))}; }},
relocate_ptr{[](storage_ptr_t dst_ptr, storage_ptr_t src_ptr) -> void {
::new (dst_ptr) C{std::move(*static_cast<C*>(src_ptr))};
static_cast<C*>(src_ptr)->~C();
}},
destructor_ptr{[](storage_ptr_t src_ptr) -> void { static_cast<C*>(src_ptr)->~C(); }} {}
vtable(const vtable&) = delete;
vtable(vtable&&) = delete;
vtable& operator=(const vtable&) = delete;
vtable& operator=(vtable&&) = delete;
~vtable() = default;
};
template <class R, class... Args>
#if __cplusplus >= 201703L
inline constexpr
#endif
vtable<R, Args...>
empty_vtable{};
template <size_t DstCap, size_t DstAlign, size_t SrcCap, size_t SrcAlign>
struct is_valid_inplace_dst : std::true_type {
static_assert(DstCap >= SrcCap, "Can't squeeze larger inplace_function into a smaller one");
static_assert(DstAlign % SrcAlign == 0, "Incompatible inplace_function alignments");
};
// C++11 MSVC compatible implementation of std::is_invocable_r.
template <class R>
void accept(R);
template <class, class R, class F, class... Args>
struct is_invocable_r_impl : std::false_type {};
template <class F, class... Args>
struct is_invocable_r_impl<decltype(std::declval<F>()(std::declval<Args>()...), void()), void, F, Args...> : std::true_type {};
template <class F, class... Args>
struct is_invocable_r_impl<decltype(std::declval<F>()(std::declval<Args>()...), void()), const void, F, Args...> : std::true_type {
};
template <class R, class F, class... Args>
struct is_invocable_r_impl<decltype(accept<R>(std::declval<F>()(std::declval<Args>()...))), R, F, Args...> : std::true_type {};
template <class R, class F, class... Args>
using is_invocable_r = is_invocable_r_impl<void, R, F, Args...>;
// Note about default alignment: This class used to employ
// std::aligned_storage<Capacity>, which is now deprecated and got removed. To
// avoid behavior changes, the new implementation mimics the default alignment
// of std::aligned_storage, as per the link below.
// https://source.chromium.org/chromium/chromium/src/+/main:third_party/libc++/src/include/__type_traits/aligned_storage.h;l=49;drc=66b494f0101bb862e9e7b034f18645af4b1dd080
constexpr std::size_t GetDefaultAlignment(std::size_t capacity) {
struct struct_double {
long double lx;
};
struct struct_double4 {
double lx[4];
};
std::size_t alignments[] = {alignof(unsigned char), alignof(unsigned short), alignof(unsigned int), alignof(unsigned long),
alignof(unsigned long long), alignof(double), alignof(long double), alignof(int*),
alignof(struct_double), alignof(struct_double4)};
std::size_t max_alignment_within_capacity = 0;
for (std::size_t alignment : alignments) {
if (alignment <= capacity) {
max_alignment_within_capacity = std::max(max_alignment_within_capacity, alignment);
}
}
// The caller ensures this is non-zero via static_assert(), it's not possible to do it here.
return max_alignment_within_capacity;
}
} // namespace inplace_function_detail
template <class Signature, size_t Capacity = inplace_function_detail::InplaceFunctionDefaultCapacity,
size_t Alignment = inplace_function_detail::GetDefaultAlignment(Capacity)>
class inplace_function; // unspecified
namespace inplace_function_detail {
template <class>
struct is_inplace_function : std::false_type {};
template <class Sig, size_t Cap, size_t Align>
struct is_inplace_function<inplace_function<Sig, Cap, Align>> : std::true_type {};
} // namespace inplace_function_detail
template <class R, class... Args, size_t Capacity, size_t Alignment>
class inplace_function<R(Args...), Capacity, Alignment> {
static_assert(Alignment > 0);
using vtable_t = inplace_function_detail::vtable<R, Args...>;
using vtable_ptr_t = const vtable_t*;
template <class, size_t, size_t>
friend class inplace_function;
public:
using capacity = std::integral_constant<size_t, Capacity>;
using alignment = std::integral_constant<size_t, Alignment>;
inplace_function() noexcept : vtable_ptr_{std::addressof(inplace_function_detail::empty_vtable<R, Args...>)} {}
template <class T, class C = std::decay_t<T>,
class = std::enable_if_t<!inplace_function_detail::is_inplace_function<C>::value &&
inplace_function_detail::is_invocable_r<R, C&, Args...>::value>>
inplace_function(T&& closure) {
static_assert(std::is_copy_constructible<C>::value, "inplace_function cannot be constructed from non-copyable type");
static_assert(sizeof(C) <= Capacity, "inplace_function cannot be constructed from object with this (large) size");
static_assert(Alignment % alignof(C) == 0,
"inplace_function cannot be constructed from object with this (large) alignment");
static const vtable_t vt{inplace_function_detail::wrapper<C>{}};
vtable_ptr_ = std::addressof(vt);
::new (storage_) C{std::forward<T>(closure)};
}
template <size_t Cap, size_t Align>
inplace_function(const inplace_function<R(Args...), Cap, Align>& other)
: inplace_function(other.vtable_ptr_, other.vtable_ptr_->copy_ptr, other.storage_) {
static_assert(inplace_function_detail::is_valid_inplace_dst<Capacity, Alignment, Cap, Align>::value,
"conversion not allowed");
}
template <size_t Cap, size_t Align>
inplace_function(inplace_function<R(Args...), Cap, Align>&& other) noexcept
: inplace_function(other.vtable_ptr_, other.vtable_ptr_->relocate_ptr, other.storage_) {
static_assert(inplace_function_detail::is_valid_inplace_dst<Capacity, Alignment, Cap, Align>::value,
"conversion not allowed");
other.vtable_ptr_ = std::addressof(inplace_function_detail::empty_vtable<R, Args...>);
}
inplace_function(std::nullptr_t) noexcept : vtable_ptr_{std::addressof(inplace_function_detail::empty_vtable<R, Args...>)} {}
inplace_function(const inplace_function& other) : vtable_ptr_{other.vtable_ptr_} {
vtable_ptr_->copy_ptr(storage_, other.storage_);
}
inplace_function(inplace_function&& other) noexcept
: vtable_ptr_{std::exchange(other.vtable_ptr_, std::addressof(inplace_function_detail::empty_vtable<R, Args...>))} {
vtable_ptr_->relocate_ptr(storage_, other.storage_);
}
inplace_function& operator=(std::nullptr_t) noexcept {
vtable_ptr_->destructor_ptr(storage_);
vtable_ptr_ = std::addressof(inplace_function_detail::empty_vtable<R, Args...>);
return *this;
}
inplace_function& operator=(inplace_function other) noexcept {
vtable_ptr_->destructor_ptr(storage_);
vtable_ptr_ = std::exchange(other.vtable_ptr_, std::addressof(inplace_function_detail::empty_vtable<R, Args...>));
vtable_ptr_->relocate_ptr(storage_, other.storage_);
return *this;
}
~inplace_function() { vtable_ptr_->destructor_ptr(storage_); }
R operator()(Args... args) const { return vtable_ptr_->invoke_ptr(storage_, std::forward<Args>(args)...); }
constexpr bool operator==(std::nullptr_t) const noexcept { return !operator bool(); }
constexpr bool operator!=(std::nullptr_t) const noexcept { return operator bool(); }
explicit constexpr operator bool() const noexcept {
return vtable_ptr_ != std::addressof(inplace_function_detail::empty_vtable<R, Args...>);
}
void swap(inplace_function& other) noexcept {
if (this == std::addressof(other)) return;
alignas(Alignment) std::byte tmp[Capacity];
vtable_ptr_->relocate_ptr(tmp, storage_);
other.vtable_ptr_->relocate_ptr(storage_, other.storage_);
vtable_ptr_->relocate_ptr(other.storage_, tmp);
std::swap(vtable_ptr_, other.vtable_ptr_);
}
friend void swap(inplace_function& lhs, inplace_function& rhs) noexcept { lhs.swap(rhs); }
private:
vtable_ptr_t vtable_ptr_;
alignas(Alignment) mutable std::byte storage_[Capacity];
inplace_function(vtable_ptr_t vtable_ptr, typename vtable_t::process_ptr_t process_ptr,
typename vtable_t::storage_ptr_t storage_ptr)
: vtable_ptr_{vtable_ptr} {
process_ptr(storage_, storage_ptr);
}
};
} // namespace stdext