mirror of
https://github.com/pocketpy/pocketpy
synced 2025-10-20 19:40:18 +00:00
478 lines
17 KiB
C++
478 lines
17 KiB
C++
/***************************************************************************
|
||
* Copyright (c) Sylvain Corlay and Johan Mabille and Wolf Vollprecht *
|
||
* Copyright (c) QuantStack *
|
||
* *
|
||
* Distributed under the terms of the BSD 3-Clause License. *
|
||
* *
|
||
* The full license is in the file LICENSE, distributed with this software. *
|
||
****************************************************************************/
|
||
|
||
#ifndef XTL_ANY_HPP
|
||
#define XTL_ANY_HPP
|
||
|
||
#include <exception>
|
||
#include <stdexcept>
|
||
#include <type_traits>
|
||
#include <typeinfo>
|
||
|
||
#include "xtl/xmeta_utils.hpp"
|
||
|
||
namespace xtl
|
||
{
|
||
/**************************************
|
||
* Implementation of C++17's std::any *
|
||
**************************************/
|
||
|
||
// Copyright (c) 2016 Denilson das Mercês Amorim
|
||
//
|
||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||
|
||
class bad_any_cast : public std::bad_cast
|
||
{
|
||
public:
|
||
|
||
const char* what() const noexcept override
|
||
{
|
||
return "bad any cast";
|
||
}
|
||
};
|
||
|
||
namespace detail {
|
||
inline static void check_any_cast(const void* p) {
|
||
if (p == nullptr) {
|
||
#if defined(XTL_NO_EXCEPTIONS)
|
||
std::fprintf(stderr, "bad_any_cast\n");
|
||
std::terminate();
|
||
#else
|
||
throw bad_any_cast();
|
||
#endif
|
||
}
|
||
}
|
||
} // namespace detail
|
||
|
||
class any final
|
||
{
|
||
public:
|
||
|
||
/// Constructs an object of type any with an empty state.
|
||
any()
|
||
: vtable(nullptr)
|
||
{
|
||
}
|
||
|
||
/// Constructs an object of type any with an equivalent state as other.
|
||
any(const any& rhs)
|
||
: vtable(rhs.vtable)
|
||
{
|
||
if (!rhs.empty())
|
||
{
|
||
rhs.vtable->copy(rhs.storage, this->storage);
|
||
}
|
||
}
|
||
|
||
/// Constructs an object of type any with a state equivalent to the original state of other.
|
||
/// rhs is left in a valid but otherwise unspecified state.
|
||
any(any&& rhs) noexcept
|
||
: vtable(rhs.vtable)
|
||
{
|
||
if (!rhs.empty())
|
||
{
|
||
rhs.vtable->move(rhs.storage, this->storage);
|
||
rhs.vtable = nullptr;
|
||
}
|
||
}
|
||
|
||
/// Same effect as this->clear().
|
||
~any()
|
||
{
|
||
this->clear();
|
||
}
|
||
|
||
/// Constructs an object of type any that contains an object of type T direct-initialized with std::forward<ValueType>(value).
|
||
///
|
||
/// T shall satisfy the CopyConstructible requirements, otherwise the program is ill-formed.
|
||
/// This is because an `any` may be copy constructed into another `any` at any time, so a copy should always be allowed.
|
||
template <typename ValueType, typename = typename std::enable_if<!std::is_same<typename std::decay<ValueType>::type, any>::value>::type>
|
||
any(ValueType&& value)
|
||
{
|
||
static_assert(std::is_copy_constructible<typename std::decay<ValueType>::type>::value,
|
||
"T shall satisfy the CopyConstructible requirements.");
|
||
this->construct(std::forward<ValueType>(value));
|
||
}
|
||
|
||
/// Has the same effect as any(rhs).swap(*this). No effects if an exception is thrown.
|
||
any& operator=(const any& rhs)
|
||
{
|
||
any(rhs).swap(*this);
|
||
return *this;
|
||
}
|
||
|
||
/// Has the same effect as any(std::move(rhs)).swap(*this).
|
||
///
|
||
/// The state of *this is equivalent to the original state of rhs and rhs is left in a valid
|
||
/// but otherwise unspecified state.
|
||
any& operator=(any&& rhs) noexcept
|
||
{
|
||
any(std::move(rhs)).swap(*this);
|
||
return *this;
|
||
}
|
||
|
||
/// Has the same effect as any(std::forward<ValueType>(value)).swap(*this). No effect if a exception is thrown.
|
||
///
|
||
/// T shall satisfy the CopyConstructible requirements, otherwise the program is ill-formed.
|
||
/// This is because an `any` may be copy constructed into another `any` at any time, so a copy should always be allowed.
|
||
template <typename ValueType, typename = typename std::enable_if<!std::is_same<typename std::decay<ValueType>::type, any>::value>::type>
|
||
any& operator=(ValueType&& value)
|
||
{
|
||
static_assert(std::is_copy_constructible<typename std::decay<ValueType>::type>::value,
|
||
"T shall satisfy the CopyConstructible requirements.");
|
||
any(std::forward<ValueType>(value)).swap(*this);
|
||
return *this;
|
||
}
|
||
|
||
/// If not empty, destroys the contained object.
|
||
void clear() noexcept
|
||
{
|
||
if (!empty())
|
||
{
|
||
this->vtable->destroy(storage);
|
||
this->vtable = nullptr;
|
||
}
|
||
}
|
||
|
||
/// C++17 equivalent of clear
|
||
void reset() noexcept
|
||
{
|
||
clear();
|
||
}
|
||
|
||
/// Returns true if *this has no contained object, otherwise false.
|
||
bool empty() const noexcept
|
||
{
|
||
return this->vtable == nullptr;
|
||
}
|
||
|
||
/// C++17 equivalent of !empty()
|
||
bool has_value() const noexcept
|
||
{
|
||
return !empty();
|
||
}
|
||
|
||
/// If *this has a contained object of type T, typeid(T); otherwise typeid(void).
|
||
const std::type_info& type() const noexcept
|
||
{
|
||
return empty() ? typeid(void) : this->vtable->type();
|
||
}
|
||
|
||
/// Exchange the states of *this and rhs.
|
||
void swap(any& rhs) noexcept
|
||
{
|
||
if (this->vtable != rhs.vtable)
|
||
{
|
||
any tmp(std::move(rhs));
|
||
|
||
// move from *this to rhs.
|
||
rhs.vtable = this->vtable;
|
||
if (this->vtable != nullptr)
|
||
{
|
||
this->vtable->move(this->storage, rhs.storage);
|
||
//this->vtable = nullptr; -- uneeded, see below
|
||
}
|
||
|
||
// move from tmp (previously rhs) to *this.
|
||
this->vtable = tmp.vtable;
|
||
if (tmp.vtable != nullptr)
|
||
{
|
||
tmp.vtable->move(tmp.storage, this->storage);
|
||
tmp.vtable = nullptr;
|
||
}
|
||
}
|
||
else // same types
|
||
{
|
||
if (this->vtable != nullptr)
|
||
this->vtable->swap(this->storage, rhs.storage);
|
||
}
|
||
}
|
||
|
||
private: // Storage and Virtual Method Table
|
||
|
||
union storage_union {
|
||
using stack_storage_t = typename std::aligned_storage<2 * sizeof(void*), std::alignment_of<void*>::value>::type;
|
||
|
||
void* dynamic;
|
||
stack_storage_t stack; // 2 words for e.g. shared_ptr
|
||
};
|
||
|
||
/// Base VTable specification.
|
||
struct vtable_type
|
||
{
|
||
// Note: The caller is responssible for doing .vtable = nullptr after destructful operations
|
||
// such as destroy() and/or move().
|
||
|
||
/// The type of the object this vtable is for.
|
||
const std::type_info& (*type)() noexcept;
|
||
|
||
/// Destroys the object in the union.
|
||
/// The state of the union after this call is unspecified, caller must ensure not to use src anymore.
|
||
void (*destroy)(storage_union&) noexcept;
|
||
|
||
/// Copies the **inner** content of the src union into the yet unitialized dest union.
|
||
/// As such, both inner objects will have the same state, but on separate memory locations.
|
||
void (*copy)(const storage_union& src, storage_union& dest);
|
||
|
||
/// Moves the storage from src to the yet unitialized dest union.
|
||
/// The state of src after this call is unspecified, caller must ensure not to use src anymore.
|
||
void (*move)(storage_union& src, storage_union& dest) noexcept;
|
||
|
||
/// Exchanges the storage between lhs and rhs.
|
||
void (*swap)(storage_union& lhs, storage_union& rhs) noexcept;
|
||
};
|
||
|
||
/// VTable for dynamically allocated storage.
|
||
template <typename T>
|
||
struct vtable_dynamic
|
||
{
|
||
static const std::type_info& type() noexcept
|
||
{
|
||
return typeid(T);
|
||
}
|
||
|
||
static void destroy(storage_union& storage) noexcept
|
||
{
|
||
//assert(reinterpret_cast<T*>(storage.dynamic));
|
||
delete reinterpret_cast<T*>(storage.dynamic);
|
||
}
|
||
|
||
static void copy(const storage_union& src, storage_union& dest)
|
||
{
|
||
dest.dynamic = new T(*reinterpret_cast<const T*>(src.dynamic));
|
||
}
|
||
|
||
static void move(storage_union& src, storage_union& dest) noexcept
|
||
{
|
||
dest.dynamic = src.dynamic;
|
||
src.dynamic = nullptr;
|
||
}
|
||
|
||
static void swap(storage_union& lhs, storage_union& rhs) noexcept
|
||
{
|
||
// just exchage the storage pointers.
|
||
std::swap(lhs.dynamic, rhs.dynamic);
|
||
}
|
||
};
|
||
|
||
/// VTable for stack allocated storage.
|
||
template <typename T>
|
||
struct vtable_stack
|
||
{
|
||
static const std::type_info& type() noexcept
|
||
{
|
||
return typeid(T);
|
||
}
|
||
|
||
static void destroy(storage_union& storage) noexcept
|
||
{
|
||
reinterpret_cast<T*>(&storage.stack)->~T();
|
||
}
|
||
|
||
static void copy(const storage_union& src, storage_union& dest)
|
||
{
|
||
new (&dest.stack) T(reinterpret_cast<const T&>(src.stack));
|
||
}
|
||
|
||
static void move(storage_union& src, storage_union& dest) noexcept
|
||
{
|
||
// one of the conditions for using vtable_stack is a nothrow move constructor,
|
||
// so this move constructor will never throw a exception.
|
||
new (&dest.stack) T(std::move(reinterpret_cast<T&>(src.stack)));
|
||
destroy(src);
|
||
}
|
||
|
||
static void swap(storage_union& lhs, storage_union& rhs) noexcept
|
||
{
|
||
storage_union tmp_storage;
|
||
move(rhs, tmp_storage);
|
||
move(lhs, rhs);
|
||
move(tmp_storage, lhs);
|
||
}
|
||
};
|
||
|
||
/// Whether the type T must be dynamically allocated or can be stored on the stack.
|
||
template <typename T>
|
||
struct requires_allocation : std::integral_constant<bool,
|
||
!(std::is_nothrow_move_constructible<T>::value // N4562 <20>6.3/3 [any.class]
|
||
&& sizeof(T) <= sizeof(storage_union::stack) && std::alignment_of<T>::value <= std::alignment_of<storage_union::stack_storage_t>::value)>
|
||
{
|
||
};
|
||
|
||
/// Returns the pointer to the vtable of the type T.
|
||
template <typename T>
|
||
static vtable_type* vtable_for_type()
|
||
{
|
||
using VTableType = typename std::conditional<requires_allocation<T>::value, vtable_dynamic<T>, vtable_stack<T>>::type;
|
||
static vtable_type table = {
|
||
VTableType::type, VTableType::destroy,
|
||
VTableType::copy, VTableType::move,
|
||
VTableType::swap,
|
||
};
|
||
return &table;
|
||
}
|
||
|
||
protected:
|
||
template <typename T>
|
||
friend const T* any_cast(const any* operand) noexcept;
|
||
template <typename T>
|
||
friend T* any_cast(any* operand) noexcept;
|
||
|
||
/// Same effect as is_same(this->type(), t);
|
||
bool is_typed(const std::type_info& t) const
|
||
{
|
||
return is_same(this->type(), t);
|
||
}
|
||
|
||
/// Checks if two type infos are the same.
|
||
///
|
||
/// If ANY_IMPL_FAST_TYPE_INFO_COMPARE is defined, checks only the address of the
|
||
/// type infos, otherwise does an actual comparision. Checking addresses is
|
||
/// only a valid approach when there's no interaction with outside sources
|
||
/// (other shared libraries and such).
|
||
static bool is_same(const std::type_info& a, const std::type_info& b)
|
||
{
|
||
#ifdef ANY_IMPL_FAST_TYPE_INFO_COMPARE
|
||
return &a == &b;
|
||
#else
|
||
return a == b;
|
||
#endif
|
||
}
|
||
|
||
/// Casts (with no type_info checks) the storage pointer as const T*.
|
||
template <typename T>
|
||
const T* cast() const noexcept
|
||
{
|
||
return requires_allocation<typename std::decay<T>::type>::value ? reinterpret_cast<const T*>(storage.dynamic) : reinterpret_cast<const T*>(&storage.stack);
|
||
}
|
||
|
||
/// Casts (with no type_info checks) the storage pointer as T*.
|
||
template <typename T>
|
||
T* cast() noexcept
|
||
{
|
||
return requires_allocation<typename std::decay<T>::type>::value ? reinterpret_cast<T*>(storage.dynamic) : reinterpret_cast<T*>(&storage.stack);
|
||
}
|
||
|
||
private:
|
||
|
||
storage_union storage; // on offset(0) so no padding for align
|
||
vtable_type* vtable;
|
||
|
||
/// Chooses between stack and dynamic allocation for the type decay_t<ValueType>,
|
||
/// assigns the correct vtable, and constructs the object on our storage.
|
||
template <typename ValueType>
|
||
void construct(ValueType&& value)
|
||
{
|
||
using T = typename std::decay<ValueType>::type;
|
||
|
||
this->vtable = vtable_for_type<T>();
|
||
|
||
return xtl::mpl::static_if<requires_allocation<T>::value>([&](auto self)
|
||
{
|
||
self(*this).storage.dynamic = new T(std::forward<ValueType>(value));
|
||
}, /*else*/ [&](auto self)
|
||
{
|
||
new (&self(*this).storage.stack) T(std::forward<ValueType>(value));
|
||
});
|
||
}
|
||
};
|
||
|
||
|
||
namespace detail
|
||
{
|
||
template <typename ValueType>
|
||
inline ValueType any_cast_move_if_true(typename std::remove_reference<ValueType>::type* p, std::true_type)
|
||
{
|
||
return std::move(*p);
|
||
}
|
||
|
||
template <typename ValueType>
|
||
inline ValueType any_cast_move_if_true(typename std::remove_reference<ValueType>::type* p, std::false_type)
|
||
{
|
||
return *p;
|
||
}
|
||
}
|
||
|
||
/// Performs *any_cast<add_const_t<remove_reference_t<ValueType>>>(&operand), or throws bad_any_cast on failure.
|
||
template <typename ValueType>
|
||
inline ValueType any_cast(const any& operand)
|
||
{
|
||
auto p = any_cast<typename std::add_const<typename std::remove_reference<ValueType>::type>::type>(&operand);
|
||
detail::check_any_cast(p);
|
||
return *p;
|
||
}
|
||
|
||
/// Performs *any_cast<remove_reference_t<ValueType>>(&operand), or throws bad_any_cast on failure.
|
||
template <typename ValueType>
|
||
inline ValueType any_cast(any& operand)
|
||
{
|
||
auto p = any_cast<typename std::remove_reference<ValueType>::type>(&operand);
|
||
detail::check_any_cast(p);
|
||
return *p;
|
||
}
|
||
|
||
///
|
||
/// If ANY_IMPL_ANYCAST_MOVEABLE is not defined, does as N4562 specifies:
|
||
/// Performs *any_cast<remove_reference_t<ValueType>>(&operand), or throws bad_any_cast on failure.
|
||
///
|
||
/// If ANY_IMPL_ANYCAST_MOVEABLE is defined, does as LWG Defect 2509 specifies:
|
||
/// If ValueType is MoveConstructible and isn't a lvalue reference, performs
|
||
/// std::move(*any_cast<remove_reference_t<ValueType>>(&operand)), otherwise
|
||
/// *any_cast<remove_reference_t<ValueType>>(&operand). Throws bad_any_cast on failure.
|
||
///
|
||
template <typename ValueType>
|
||
inline ValueType any_cast(any&& operand)
|
||
{
|
||
#ifdef ANY_IMPL_ANY_CAST_MOVEABLE
|
||
// https://cplusplus.github.io/LWG/lwg-active.html#2509
|
||
using can_move = std::integral_constant<bool,
|
||
std::is_move_constructible<ValueType>::value && !std::is_lvalue_reference<ValueType>::value>;
|
||
#else
|
||
using can_move = std::false_type;
|
||
#endif
|
||
|
||
auto p = any_cast<typename std::remove_reference<ValueType>::type>(&operand);
|
||
detail::check_any_cast(p);
|
||
return detail::any_cast_move_if_true<ValueType>(p, can_move());
|
||
}
|
||
|
||
/// If operand != nullptr && operand->type() == typeid(ValueType), a pointer to the object
|
||
/// contained by operand, otherwise nullptr.
|
||
template <typename T>
|
||
inline const T* any_cast(const any* operand) noexcept
|
||
{
|
||
if (operand == nullptr || !operand->is_typed(typeid(T)))
|
||
return nullptr;
|
||
else
|
||
return operand->cast<T>();
|
||
}
|
||
|
||
/// If operand != nullptr && operand->type() == typeid(ValueType), a pointer to the object
|
||
/// contained by operand, otherwise nullptr.
|
||
template <typename T>
|
||
inline T* any_cast(any* operand) noexcept
|
||
{
|
||
if (operand == nullptr || !operand->is_typed(typeid(T)))
|
||
return nullptr;
|
||
else
|
||
return operand->cast<T>();
|
||
}
|
||
}
|
||
|
||
namespace std
|
||
{
|
||
inline void swap(xtl::any& lhs, xtl::any& rhs) noexcept
|
||
{
|
||
lhs.swap(rhs);
|
||
}
|
||
}
|
||
|
||
#endif
|