/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright The KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see .
*/
// This code is a modified version of the GCC standard library implementation (see original
// licence below):
// Copyright (C) 2014-2024 Free Software Foundation, Inc.
//
// This file is part of the GNU ISO C++ Library. This library is free
// software; you can redistribute it and/or modify it under the
// terms of the GNU General Public License as published by the
// Free Software Foundation; either version 3, or (at your option)
// any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// Under Section 7 of GPL version 3, you are granted additional
// permissions described in the GCC Runtime Library Exception, version
// 3.1, as published by the Free Software Foundation.
//
// You should have received a copy of the GNU General Public License and
// a copy of the GCC Runtime Library Exception along with this program;
// see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
// .
/**
* @file ki_any.h
* @brief An implementation of std::any_cast, which uses type_info::hash_code to check validity of
* cast types.
*
* This is required as Clang compares types as being equivalent based on their type_info pointer
* locations. These are not guaranteed to be the same with identical types linked in multiple
* targets from shared libraries. The current Clang implementation of type_info::hash_code is
* based on the type names, which should be consistent across translation units.
*/
#ifndef INCLUDE_KI_ANY_H_
#define INCLUDE_KI_ANY_H_
#include
#include
#include
#include
namespace ki
{
/*
* Disambiguation helpers
*/
template
inline constexpr bool is_in_place_type_v = false;
template
inline constexpr bool is_in_place_type_v> = true;
/**
* Exception class thrown by a failed @c any_cast
*/
class bad_any_cast final : public std::bad_cast
{
public:
const char* what() const noexcept override { return "bad ki::any_cast"; }
};
/**
* A type-safe container of any type.
*
* An `any` object's state is either empty or it stores a contained object
* of CopyConstructible type.
*/
class any
{
// Holds either a pointer to a heap object or the contained object itself
union Storage
{
constexpr Storage() : m_ptr{ nullptr } {}
// Prevent trivial copies of this type as the buffer might hold a non-POD
Storage( const Storage& ) = delete;
Storage& operator=( const Storage& ) = delete;
void* m_ptr;
unsigned char m_buffer[sizeof( m_ptr )];
};
template ,
bool Fits = ( sizeof( T ) <= sizeof( Storage ) )
&& ( alignof( T ) <= alignof( Storage ) )>
using Use_Internal_Storage = std::integral_constant;
template
struct Manager_Internal; // uses small-object optimization
template
struct Manager_External; // creates contained object on the heap
template
using Manager = std::conditional_t::value, Manager_Internal,
Manager_External>;
template >
using decay_if_not_any = std::enable_if_t, V>;
/// Emplace with an object created from @p args as the contained object.
template >
void do_emplace( Args&&... args )
{
reset();
Mgr::do_create( m_storage, std::forward( args )... );
m_manager = &Mgr::m_manage_fn;
}
/// Emplace with an object created from @p il and @p args as the contained object.
template >
void do_emplace( std::initializer_list il, Args&&... args )
{
reset();
Mgr::do_create( m_storage, il, std::forward( args )... );
m_manager = &Mgr::m_manage_fn;
}
template
using any_constructible =
std::enable_if && std::is_constructible_v,
Res>;
template
using any_constructible_t = typename any_constructible::type;
template
using any_emplace_t = typename any_constructible::type;
public:
/// Default constructor, creates an empty object.
constexpr any() noexcept : m_manager( nullptr ) {}
/// Copy constructor, copies the state of @p other.
any( const any& other )
{
if( !other.has_value() )
{
m_manager = nullptr;
}
else
{
Arg arg;
arg.m_any = this;
other.m_manager( Op_Clone, &other, &arg );
}
}
/// Move constructor, transfer the state from @p other.
any( any&& other ) noexcept
{
if( !other.has_value() )
{
m_manager = nullptr;
}
else
{
Arg arg;
arg.m_any = this;
other.m_manager( Op_Xfer, &other, &arg );
}
}
/// Construct with a copy of @p value as the contained object.
template , typename Mgr = Manager,
std::enable_if_t && !is_in_place_type_v, bool> =
true>
any( T&& value ) : m_manager( &Mgr::m_manage_fn )
{
Mgr::do_create( m_storage, std::forward( value ) );
}
/// Construct with an object created from @p args as the contained object.
template , typename Mgr = Manager,
any_constructible_t = false>
explicit any( std::in_place_type_t, Args&&... args ) : m_manager( &Mgr::m_manage_fn )
{
Mgr::do_create( m_storage, std::forward( args )... );
}
/// Construct with an object created from @p il and @p args as the contained object.
template ,
typename Mgr = Manager,
any_constructible_t&, Args&&...> = false>
explicit any( std::in_place_type_t, std::initializer_list il, Args&&... args ) :
m_manager( &Mgr::m_manage_fn )
{
Mgr::do_create( m_storage, il, std::forward( args )... );
}
/// Destructor, calls @c reset().
~any() { reset(); }
/// Copy the state of another object.
any& operator=( const any& rhs )
{
*this = any( rhs );
return *this;
}
/// Move assignment operator.
any& operator=( any&& rhs ) noexcept
{
if( !rhs.has_value() )
{
reset();
}
else if( this != &rhs )
{
reset();
Arg arg;
arg.m_any = this;
rhs.m_manager( Op_Xfer, &rhs, &arg );
}
return *this;
}
/// Store a copy of @p rhs as the contained object.
template
std::enable_if_t>, any&> operator=( T&& rhs )
{
*this = any( std::forward( rhs ) );
return *this;
}
/// Emplace with an object created from @p args as the contained object.
template
any_emplace_t, Args...> emplace( Args&&... args )
{
using V = std::decay_t;
do_emplace( std::forward( args )... );
return *any::Manager::do_access( m_storage );
}
/// Emplace with an object created from @p il and @p args as the contained object.
template
any_emplace_t, std::initializer_list&, Args&&...>
emplace( std::initializer_list il, Args&&... args )
{
using V = std::decay_t;
do_emplace( il, std::forward( args )... );
return *any::Manager::do_access( m_storage );
}
/// If not empty, destroys the contained object.
void reset() noexcept
{
if( has_value() )
{
m_manager( Op_Destroy, this, nullptr );
m_manager = nullptr;
}
}
/// Exchange state with another object.
void swap( any& rhs ) noexcept
{
if( !has_value() && !rhs.has_value() )
return;
if( has_value() && rhs.has_value() )
{
if( this == &rhs )
return;
any tmp;
Arg arg;
arg.m_any = &tmp;
rhs.m_manager( Op_Xfer, &rhs, &arg );
arg.m_any = &rhs;
m_manager( Op_Xfer, this, &arg );
arg.m_any = this;
tmp.m_manager( Op_Xfer, &tmp, &arg );
}
else
{
any* empty = !has_value() ? this : &rhs;
any* full = !has_value() ? &rhs : this;
Arg arg;
arg.m_any = empty;
full->m_manager( Op_Xfer, full, &arg );
}
}
/// Report whether there is a contained object or not.
bool has_value() const noexcept { return m_manager != nullptr; }
/// The @c typeid of the contained object, or @c typeid(void) if empty.
const std::type_info& type() const noexcept
{
if( !has_value() )
return typeid( void );
Arg arg;
m_manager( Op_Get_Type_Info, this, &arg );
return *arg.m_typeinfo;
}
/// @cond undocumented
template
static constexpr bool is_valid_any_cast()
{
return std::is_reference_v || std::is_copy_constructible_v;
}
/// @endcond
private:
enum Op
{
Op_Access,
Op_Get_Type_Info,
Op_Clone,
Op_Destroy,
Op_Xfer
};
union Arg
{
void* m_obj;
const std::type_info* m_typeinfo;
any* m_any;
};
void ( *m_manager )( Op, const any*, Arg* );
Storage m_storage;
/// @cond undocumented
template
friend void* any_caster( const any* any );
/// @endcond
// Manages in-place contained object
template
struct Manager_Internal
{
static void m_manage_fn( Op which, const any* any, Arg* arg );
template
static void do_create( Storage& storage, U&& value )
{
void* addr = &storage.m_buffer;
::new( addr ) T( std::forward( value ) );
}
template
static void do_create( Storage& storage, Args&&... args )
{
void* addr = &storage.m_buffer;
::new( addr ) T( std::forward( args )... );
}
static T* do_access( const Storage& storage )
{
// The contained object is in storage.m_buffer
const void* addr = &storage.m_buffer;
return static_cast( const_cast( addr ) );
}
};
// Manages externally (heap) contained object
template
struct Manager_External
{
static void m_manage_fn( Op which, const any* any, Arg* arg );
template
static void do_create( Storage& storage, U&& value )
{
storage.m_ptr = new T( std::forward( value ) );
}
template
static void do_create( Storage& storage, Args&&... args )
{
storage.m_ptr = new T( std::forward( args )... );
}
static T* do_access( const Storage& storage )
{
// The contained object is in *storage.m_ptr
return static_cast( storage.m_ptr );
}
};
};
/// Exchange the states of two @c any objects.
inline void swap( any& x, any& y ) noexcept
{
x.swap( y );
}
/// Create a `any` holding a `T` constructed from `args...`.
template
std::enable_if_t, Args...>, any>
make_any( Args&&... args )
{
return any( std::in_place_type, std::forward( args )... );
}
/// Create an `any` holding a `T` constructed from `il` and `args...`.
template
std::enable_if_t<
std::is_constructible_v, std::initializer_list&, Args...>,
any>
make_any( std::initializer_list il, Args&&... args )
{
return any( std::in_place_type, il, std::forward( args )... );
}
/**
* Access the contained object.
*
* @tparam ValueType A const-reference or CopyConstructible type.
* @param any The object to access.
* @return The contained object.
* @throw bad_any_cast If
* any.type() != typeid(remove_reference_t)
*
*/
template
ValueType any_cast( const any& any )
{
using U = std::remove_cvref_t;
static_assert( any::is_valid_any_cast(),
"Template argument must be a reference or CopyConstructible type" );
static_assert( std::is_constructible_v,
"Template argument must be constructible from a const value" );
auto p = any_cast( &any );
if( p )
return static_cast( *p );
throw bad_any_cast{};
}
/**
* Access the contained object.
*
* @tparam ValueType A reference or CopyConstructible type.
* @param any The object to access.
* @return The contained object.
* @throw bad_any_cast If
* any.type() != typeid(remove_reference_t)
*
* @{
*/
template
ValueType any_cast( any& any )
{
using U = std::remove_cvref_t;
static_assert( any::is_valid_any_cast(),
"Template argument must be a reference or CopyConstructible type" );
static_assert( std::is_constructible_v,
"Template argument must be constructible from an lvalue" );
auto p = any_cast( &any );
if( p )
return static_cast( *p );
throw bad_any_cast{};
}
template
ValueType any_cast( any&& any )
{
using U = std::remove_cvref_t;
static_assert( any::is_valid_any_cast(),
"Template argument must be a reference or CopyConstructible type" );
static_assert( std::is_constructible_v,
"Template argument must be constructible from an rvalue" );
auto p = any_cast( &any );
if( p )
return static_cast( std::move( *p ) );
throw bad_any_cast{};
}
/// @}
/// @cond undocumented
template
void* any_caster( const any* any )
{
// any_cast returns non-null if any->type() == typeid(T) and
// typeid(T) ignores cv-qualifiers so remove them:
using U = std::remove_cv_t;
if constexpr( !std::is_same_v, U> )
{
// The contained value has a decayed type, so if decay_t is not U,
// then it's not possible to have a contained value of type U
return nullptr;
}
else if constexpr( !std::is_copy_constructible_v )
{
// Only copy constructible types can be used for contained values
return nullptr;
}
else if( any->m_manager == &any::Manager::m_manage_fn
|| any->type().hash_code() == typeid( T ).hash_code() )
{
return any::Manager::do_access( any->m_storage );
}
return nullptr;
}
/// @endcond
/**
* Access the contained object.
*
* @tparam ValueType The type of the contained object.
* @param any A pointer to the object to access.
* @return The address of the contained object if
* any != nullptr && any.type() == typeid(ValueType)
*
, otherwise a null pointer.
*
* @{
*/
template
const ValueType* any_cast( const any* any ) noexcept
{
static_assert( !std::is_void_v );
// As an optimization, don't bother instantiating any_caster for
// function types, since std::any can only hold objects
if constexpr( std::is_object_v )
{
if( any )
return static_cast( any_caster( any ) );
}
return nullptr;
}
template
ValueType* any_cast( any* any ) noexcept
{
static_assert( !std::is_void_v );
if constexpr( std::is_object_v )
if( any )
return static_cast( any_caster( any ) );
return nullptr;
}
/// @}
template
void any::Manager_Internal::m_manage_fn( Op which, const any* any, Arg* arg )
{
// The contained object is in m_storage.m_buffer
auto ptr = reinterpret_cast( &any->m_storage.m_buffer );
switch( which )
{
case Op_Access: arg->m_obj = const_cast( ptr ); break;
case Op_Get_Type_Info: arg->m_typeinfo = &typeid( T ); break;
case Op_Clone:
::new( &arg->m_any->m_storage.m_buffer ) T( *ptr );
arg->m_any->m_manager = any->m_manager;
break;
case Op_Destroy: ptr->~T(); break;
case Op_Xfer:
::new( &arg->m_any->m_storage.m_buffer ) T( std::move( *const_cast( ptr ) ) );
ptr->~T();
arg->m_any->m_manager = any->m_manager;
const_cast( any )->m_manager = nullptr;
break;
}
}
template
void any::Manager_External::m_manage_fn( Op which, const any* any, Arg* arg )
{
// The contained object is *m_storage.m_ptr
auto ptr = static_cast( any->m_storage.m_ptr );
switch( which )
{
case Op_Access: arg->m_obj = const_cast( ptr ); break;
case Op_Get_Type_Info: arg->m_typeinfo = &typeid( T ); break;
case Op_Clone:
arg->m_any->m_storage.m_ptr = new T( *ptr );
arg->m_any->m_manager = any->m_manager;
break;
case Op_Destroy: delete ptr; break;
case Op_Xfer:
arg->m_any->m_storage.m_ptr = any->m_storage.m_ptr;
arg->m_any->m_manager = any->m_manager;
const_cast( any )->m_manager = nullptr;
break;
}
}
} // namespace ki
#endif // INCLUDE_KI_ANY_H_