diff --git a/Source/Base/include/Base/variable_container.h b/Source/Base/include/Base/variable_container.h index 42037375a..881a9ab97 100644 --- a/Source/Base/include/Base/variable_container.h +++ b/Source/Base/include/Base/variable_container.h @@ -2,17 +2,16 @@ #pragma once #include -#include #include +#include #include -#include namespace nc::base { namespace detail { struct variable_container_base { - enum class type : char + enum class type : unsigned char { dense = 0, sparse = 1, @@ -22,167 +21,133 @@ struct variable_container_base { } // namespace detail +// variable_container is a hybrid container, which can take one of 3 forms - a common value container, a dense container +// and a sparse container. It's useful when characteristics of data can dratically change in run-time and not known in +// advance. variable_container is not compatible with all idioms of STL containers and should be used with care. template -class alignas(max_align_t) variable_container +class variable_container { public: - typedef T value_type; - typedef T &reference; - typedef const T &const_reference; - + using value_type = T; + using reference = T &; + using const_reference = const T &; using type = detail::variable_container_base::type; - /** - * Construction/desctuction/assigning. - */ + // Creates a container with a specified type. + // A common value container will implicitly create the default value. variable_container(type _type = type::common); + + // Creates a container with a common value copied from the parameter. variable_container(const T &_value); + + // Creates a container with a common value moved from the parameter. variable_container(T &&_value); - variable_container(const variable_container &_rhs); - variable_container(variable_container &&_rhs) noexcept; - ~variable_container(); - const variable_container &operator=(const variable_container &_rhs); - const variable_container &operator=(variable_container &&_rhs) noexcept; - - /** - * return current container's type. - */ - type mode() const noexcept; - - /** - * reverts container to empty state with specified type. - */ + + // Returns the current container's type. + constexpr type mode() const noexcept; + + // Reverts the container to an empty state with a specified type. void reset(type _type); - /** - * for common mode return common value. - * for other modes uses at() of vector<> and unordered_map<>. - */ + // Returns a reference to an element at the specified index and throws if it doesn't exist. + // For the common mode returns the common value. + // For other modes uses .at() of vector<> and unordered_map<>. T &at(size_t _at); + + // Returns a reference to an element at the specified index and throws if it doesn't exist. + // For the common mode returns the common value. + // For other modes uses .at() of vector<> and unordered_flat_map<>. const T &at(size_t _at) const; - /** - * for common mode return common value. - * for dense mode uses vector<>::operator[]. - * for sparse mode uses unordered_map<>::find. - * precondition: the element must exist. - */ + // Returns a reference to an existing element at the specified index. + // For the common mode returns the common value. + // For the dense mode uses vector<>::operator[]. + // For the sparse mode uses unordered_flat_map<>::find (precondition: the element must exist). T &operator[](size_t _at) noexcept; + + // Returns a reference to an existing element at the specified index. + // For the common mode returns the common value. + // For the dense mode uses vector<>::operator[]. + // For the sparse mode uses unordered_flat_map<>::find (precondition: the element must exist). const T &operator[](size_t _at) const noexcept; - /** - * return amount of elements inside. - * for common mode always returns 1. - * for other modes return size of a corresponding container. - */ + // Returns the amount of elements inside the container. + // For common mode always returns 1. + // For other modes return the size of a corresponding container. size_t size() const noexcept; - /** - * returns size() == 0; - */ + // returns size() == 0; bool empty() const noexcept; - /** - * Can be used only with Dense mode, ignored otherwise. - */ + // Resizes the container to a new size. + // Can be used only with Dense mode, ignored otherwise. void resize(size_t _new_size); - /** - * if mode is Dense an _at is above current size -> will resize accordingly. - * if mode is Common will ignore _at and fill common value with _value. - */ + // Inserts the value into the container at the specified index. + // If mode is Dense and _at is above the current size the container will resize accordingly. + // If mode is Common the _at index will be ignored and the common value will be set with _value. void insert(size_t _at, const T &_value); + + // Inserts the value into the container at the specified index. + // If mode is Dense and _at is above the current size the container will resize accordingly. + // If mode is Common the _at index will be ignored and the common value will be set with _value. void insert(size_t _at, T &&_value); - /** - * for common mode return true always. - * for sparse mode checks for presence of this item. - * for dense mode checks vector bounds. - */ + // Checks at the container has an element at the specified index. + // For the common mode always returns true. + // For the sparse mode checks for presence of this item in the unordered map. + // For the dense mode checks the vector bounds. bool has(size_t _at) const noexcept; - /** - * return true if: - * - type is common or dense - * - type is sparse and map containst all keys from [0, size) - */ + // Checks if the container has no gaps in the seqence of used indices. + // Returns true if: + // - the type is common or dense + // - the type is sparse and the map contains all keys in [0, size) bool is_contiguous() const noexcept; - /** - * transforms sparse container with contiguous elements into a dense container. - * will throw a logic_error if any element is missing ( container is non contiguous ). - * if container isn't sparse type - will throw a logic_error. - */ + // Transforms a sparse container with contiguous elements into a dense container. + // Will throw a logic_error if any element is missing (i.e., the container is non contiguous). + // If container isn't of a sparse type - will throw a logic_error. void compress_contiguous(); private: using common_type = value_type; using sparse_type = robin_hood::unordered_flat_map; using dense_type = std::vector; - static constexpr std::size_t m_StorageSize = - std::max({sizeof(common_type), sizeof(sparse_type), sizeof(dense_type)}); - static_assert(alignof(max_align_t) >= alignof(common_type)); - static_assert(alignof(max_align_t) >= alignof(sparse_type)); - static_assert(alignof(max_align_t) >= alignof(dense_type)); - - common_type &Common(); - const common_type &Common() const; - sparse_type &Sparse(); - const sparse_type &Sparse() const; - dense_type &Dense(); - const dense_type &Dense() const; - - void Construct(); - void ConstructCopy(const variable_container &_rhs); - void ConstructMove(variable_container &&_rhs); - void Destruct(); - // it would be nice to change this ugly casts to C++11-style unions, but current XCode6.4 - // crashes on them. check it later. - // TODO: get rid of Common(), Sparse() and Dense() functions, move to modern union. - std::array m_Storage; - type m_Type; -}; - -template -variable_container::variable_container(type _type) : m_Type(_type) -{ - Construct(); -} + using StorageT = std::variant; -template -variable_container::variable_container(const variable_container &_rhs) : m_Type(_rhs.m_Type) -{ - ConstructCopy(_rhs); -} + common_type &Common() noexcept; + const common_type &Common() const noexcept; + sparse_type &Sparse() noexcept; + const sparse_type &Sparse() const noexcept; + dense_type &Dense() noexcept; + const dense_type &Dense() const noexcept; -template -variable_container::variable_container(variable_container &&_rhs) noexcept : m_Type(_rhs.m_Type) -{ - ConstructMove(std::move(_rhs)); -} + StorageT m_Storage; +}; template -variable_container::variable_container(const T &_value) : m_Type(type::common) +variable_container::variable_container(type _type) + : m_Storage(_type == type::common ? StorageT{std::in_place_type} + : _type == type::sparse ? StorageT{std::in_place_type} + : StorageT{std::in_place_type}) { - new(&Common()) common_type(_value); } template -variable_container::variable_container(T &&_value) : m_Type(type::common) +variable_container::variable_container(const T &_value) : m_Storage{_value} { - new(&Common()) common_type(move(_value)); } template -variable_container::~variable_container() +variable_container::variable_container(T &&_value) : m_Storage{std::move(_value)} { - Destruct(); } template -typename variable_container::type variable_container::mode() const noexcept +constexpr typename variable_container::type variable_container::mode() const noexcept { - return m_Type; + return static_cast(m_Storage.index()); } template @@ -192,248 +157,202 @@ void variable_container::reset(type _type) } template -const variable_container &variable_container::operator=(const variable_container &_rhs) +typename variable_container::common_type &variable_container::Common() noexcept { - if( m_Type != _rhs.m_Type ) { - Destruct(); - m_Type = _rhs.m_Type; - ConstructCopy(_rhs); - } - else { - if( m_Type == type::common ) - Common() = _rhs.Common(); - else if( m_Type == type::sparse ) - Sparse() = _rhs.Sparse(); - else // if( m_Type == type::dense ) - Dense() = _rhs.Dense(); - } - return *this; + assert(std::holds_alternative(m_Storage)); + return *std::get_if(&m_Storage); } template -const variable_container &variable_container::operator=(variable_container &&_rhs) noexcept +const typename variable_container::common_type &variable_container::Common() const noexcept { - if( m_Type != _rhs.m_Type ) { - Destruct(); - m_Type = _rhs.m_Type; - ConstructMove(std::move(_rhs)); - } - else { - if( m_Type == type::common ) - Common() = std::move(_rhs.Common()); - else if( m_Type == type::sparse ) - Sparse() = std::move(_rhs.Sparse()); - else // if( m_Type == type::dense ) - Dense() = std::move(_rhs.Dense()); - } - return *this; + assert(std::holds_alternative(m_Storage)); + return *std::get_if(&m_Storage); } template -typename variable_container::common_type &variable_container::Common() +typename variable_container::sparse_type &variable_container::Sparse() noexcept { - return *reinterpret_cast(m_Storage.data()); + assert(std::holds_alternative(m_Storage)); + return *std::get_if(&m_Storage); } template -const typename variable_container::common_type &variable_container::Common() const +const typename variable_container::sparse_type &variable_container::Sparse() const noexcept { - return *reinterpret_cast(m_Storage.data()); + assert(std::holds_alternative(m_Storage)); + return *std::get_if(&m_Storage); } template -typename variable_container::sparse_type &variable_container::Sparse() +typename variable_container::dense_type &variable_container::Dense() noexcept { - return *reinterpret_cast(m_Storage.data()); + assert(std::holds_alternative(m_Storage)); + return *std::get_if(&m_Storage); } template -const typename variable_container::sparse_type &variable_container::Sparse() const +const typename variable_container::dense_type &variable_container::Dense() const noexcept { - return *reinterpret_cast(m_Storage.data()); -} - -template -typename variable_container::dense_type &variable_container::Dense() -{ - return *reinterpret_cast(m_Storage.data()); -} - -template -const typename variable_container::dense_type &variable_container::Dense() const -{ - return *reinterpret_cast(m_Storage.data()); -} - -template -void variable_container::Construct() -{ - if( m_Type == type::common ) - new(&Common()) common_type; - else if( m_Type == type::sparse ) - new(&Sparse()) sparse_type; - else // if( m_Type == type::dense ) - new(&Dense()) dense_type; -} - -template -void variable_container::ConstructCopy(const variable_container &_rhs) -{ - assert(m_Type == _rhs.m_Type); - - if( m_Type == type::common ) - new(&Common()) common_type(_rhs.Common()); - else if( m_Type == type::sparse ) - new(&Sparse()) sparse_type(_rhs.Sparse()); - else // if( m_Type == type::dense ) - new(&Dense()) dense_type(_rhs.Dense()); -} - -template -void variable_container::ConstructMove(variable_container &&_rhs) -{ - assert(m_Type == _rhs.m_Type); - - if( m_Type == type::common ) - new(&Common()) common_type(std::move(_rhs.Common())); - else if( m_Type == type::sparse ) - new(&Sparse()) sparse_type(std::move(_rhs.Sparse())); - else // if( m_Type == type::dense ) - new(&Dense()) dense_type(std::move(_rhs.Dense())); -} - -template -void variable_container::Destruct() -{ - if( m_Type == type::common ) - Common().~common_type(); - else if( m_Type == type::sparse ) - Sparse().~sparse_type(); - else // if( m_Type == type::dense ) - Dense().~dense_type(); + assert(std::holds_alternative(m_Storage)); + return *std::get_if(&m_Storage); } template T &variable_container::at(size_t _at) { - if( m_Type == type::common ) - return Common(); - else if( m_Type == type::dense ) - return Dense().at(_at); - else // if( m_Type == type::sparse ) - return Sparse().at(_at); + switch( mode() ) { + case type::common: { + return Common(); + } + case type::dense: { + return Dense().at(_at); + } + case type::sparse: { + return Sparse().at(_at); + } + } } template T &variable_container::operator[](size_t _at) noexcept { - if( m_Type == type::common ) { - return Common(); - } - else if( m_Type == type::dense ) { - assert(_at < Dense().size()); - return Dense()[_at]; - } - else { // if( m_Type == type::sparse ) - auto it = Sparse().find(_at); - assert(it != Sparse().end()); - return it->second; + switch( mode() ) { + case type::common: { + return Common(); + } + case type::dense: { + assert(_at < Dense().size()); + return Dense()[_at]; + } + case type::sparse: { + auto it = Sparse().find(_at); + assert(it != Sparse().end()); + return it->second; + } } } template const T &variable_container::at(size_t _at) const { - if( m_Type == type::common ) - return Common(); - else if( m_Type == type::dense ) - return Dense().at(_at); - else // if( m_Type == type::sparse ) - return Sparse().at(_at); + switch( mode() ) { + case type::common: { + return Common(); + } + case type::dense: { + return Dense().at(_at); + } + case type::sparse: { + return Sparse().at(_at); + } + } } template const T &variable_container::operator[](size_t _at) const noexcept { - if( m_Type == type::common ) { - return Common(); - } - else if( m_Type == type::dense ) { - assert(_at < Dense().size()); - return Dense()[_at]; - } - else { // if( m_Type == type::sparse ) - auto it = Sparse().find(_at); - assert(it != Sparse().end()); - return it->second; + switch( mode() ) { + case type::common: { + return Common(); + } + case type::dense: { + assert(_at < Dense().size()); + return Dense()[_at]; + } + case type::sparse: { + auto it = Sparse().find(_at); + assert(it != Sparse().end()); + return it->second; + } } } template void variable_container::resize(size_t _new_size) { - if( m_Type == type::dense ) + if( mode() == type::dense ) Dense().resize(_new_size); } template void variable_container::insert(size_t _at, const T &_value) { - if( m_Type == type::common ) { - Common() = _value; - } - else if( m_Type == type::dense ) { - if( Dense().size() <= _at ) - Dense().resize(_at + 1); - Dense()[_at] = _value; - } - else { // if( m_Type == type::sparse ) - auto r = Sparse().insert(typename sparse_type::value_type(_at, _value)); - if( !r.second ) - r.first->second = _value; + switch( mode() ) { + case type::common: { + Common() = _value; + break; + } + case type::dense: { + dense_type &dense = Dense(); + if( dense.size() <= _at ) + dense.resize(_at + 1); + dense[_at] = _value; + break; + } + case type::sparse: { + sparse_type &sparse = Sparse(); + auto i = sparse.find(_at); + if( i == sparse.end() ) + sparse.emplace(_at, _value); + else + i->second = std::move(_value); + break; + } } } template void variable_container::insert(size_t _at, T &&_value) { - if( m_Type == type::common ) { - Common() = std::move(_value); - } - else if( m_Type == type::dense ) { - if( Dense().size() <= _at ) - Dense().resize(_at + 1); - Dense()[_at] = std::move(_value); - } - else { // if( m_Type == type::sparse ) - auto i = Sparse().find(static_cast(_at)); - if( i == std::end(Sparse()) ) - Sparse().insert(typename sparse_type::value_type(static_cast(_at), std::move(_value))); - else - i->second = std::move(_value); + switch( mode() ) { + case type::common: { + Common() = std::move(_value); + break; + } + case type::dense: { + dense_type &dense = Dense(); + if( dense.size() <= _at ) + dense.resize(_at + 1); + dense[_at] = std::move(_value); + break; + } + case type::sparse: { + sparse_type &sparse = Sparse(); + auto i = sparse.find(_at); + if( i == sparse.end() ) + sparse.emplace(_at, std::move(_value)); + else + i->second = std::move(_value); + break; + } } } template bool variable_container::has(size_t _at) const noexcept { - if( m_Type == type::common ) - return true; - else if( m_Type == type::dense ) - return _at < Dense().size(); - else // if( m_Type == type::sparse ) - return Sparse().contains(_at); + switch( mode() ) { + case type::common: + return true; + case type::dense: + return _at < Dense().size(); + case type::sparse: + return Sparse().contains(_at); + } } template size_t variable_container::size() const noexcept { - if( m_Type == type::common ) - return 1; - else if( m_Type == type::dense ) - return Dense().size(); - else // if( m_Type == type::sparse ) - return Sparse().size(); + switch( mode() ) { + case type::common: + return 1; + case type::dense: + return Dense().size(); + case type::sparse: + return Sparse().size(); + } } template @@ -445,7 +364,7 @@ bool variable_container::empty() const noexcept template bool variable_container::is_contiguous() const noexcept { - if( m_Type == type::dense || m_Type == type::common ) + if( mode() == type::dense || mode() == type::common ) return true; auto &sparse = Sparse(); @@ -460,7 +379,7 @@ bool variable_container::is_contiguous() const noexcept template void variable_container::compress_contiguous() { - if( m_Type != type::sparse ) + if( mode() != type::sparse ) throw std::logic_error("variable_container::compress_contiguous was called for a non-sparse container"); variable_container new_dense(type::dense);