/******************************************************************************** * * * This file is part of IfcOpenShell. * * * * IfcOpenShell is free software: you can redistribute it and/or modify * * it under the terms of the Lesser GNU General Public License as published by * * the Free Software Foundation, either version 3.0 of the License, or * * (at your option) any later version. * * * * IfcOpenShell 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 * * Lesser GNU General Public License for more details. * * * * You should have received a copy of the Lesser GNU General Public License * * along with this program. If not, see . * * * ********************************************************************************/ /* A dynamic sequence of variant types arranged in a way to reduce size impact due to alignment by grouping the 1 byte type indices. Using heap allocation - hence storing a pointer instead - for larger types so that the overall size of the variant - which is the maximum size of its constituents - is reduced. */ #ifndef VARIANTARRAY_H #define VARIANTARRAY_H #include #include #include #include #include #include #include "IfcException.h" namespace impl { // Trait to detect unique_ptr template struct is_unique_ptr : std::false_type {}; template struct is_unique_ptr> : std::true_type {}; /* // Trait to find index of type in parameter pack template struct TypeIndex; template struct TypeIndex : std::integral_constant {}; template struct TypeIndex : std::integral_constant::value> {}; template constexpr std::size_t TypeIndex_v = TypeIndex::value; */ // Trait to find index of type in parameter pack considering inheritance template struct TypeIndex; // Base case: When the first type in the pack is the type we're looking for, or is a base class of it template struct TypeIndex : std::integral_constant ? std::is_base_of_v, std::remove_pointer_t> : std::is_same_v) ? 0 : (TypeIndex::value == std::numeric_limits::max() ? std::numeric_limits::max() : 1 + TypeIndex::value)> {}; // Recursion termination: When the parameter pack is empty template struct TypeIndex : std::integral_constant::max()> {}; // Helper variable template template constexpr std::size_t TypeIndex_v = TypeIndex::value; // Trait to determine if a type is small enough to be stored directly template struct is_small_object { static constexpr bool value = sizeof(T) <= sizeof(void*) * 2; }; // Metafunction to transform T to unique_ptr based on size template struct TransformType { using type = typename std::conditional< is_small_object::value, T, std::unique_ptr >::type; }; // Helper to prepend a type to a tuple template struct TuplePrepend; template struct TuplePrepend> { using type = std::tuple; }; // Map types based on above size transform template struct MapTypes; template struct MapTypes { using type = typename TuplePrepend< typename TransformType::type, typename MapTypes::type >::type; }; template <> struct MapTypes<> { using type = std::tuple<>; }; template using MapTypes_t = typename MapTypes::type; // Create aligned_union from paramater pack stored in tuple for storage in variant template struct make_union_from_tuple {}; template struct make_union_from_tuple> { using type = typename std::aligned_union<0, Args...>::type; }; } template class VariantArray { public: using TypesTuple = impl::MapTypes_t; VariantArray(size_t size) : size_and_indices_(new uint8_t[size + 1]) , storage_(size ? new StorageType[size] : nullptr) { if (size) { size_and_indices_[0] = (uint8_t) size; memset(size_and_indices_ + 1, 0, sizeof(uint8_t) * size); for (size_t i = 0; i < size; ++i) { // type 0 needs to be default constructable set(i, typename std::tuple_element<0, std::tuple>::type{}); } } } VariantArray(VariantArray&& other) noexcept : size_and_indices_(other.size_and_indices_) , storage_(other.storage_) { other.size_and_indices_ = nullptr; other.storage_ = nullptr; } VariantArray& operator=(VariantArray&& other) noexcept { if (this != &other) { free_(); size_and_indices_ = other.size_and_indices_; storage_ = other.storage_; other.size_and_indices_ = nullptr; other.storage_ = nullptr; } return *this; } VariantArray(const VariantArray&) = delete; VariantArray(const VariantArray&&) = delete; VariantArray& operator= (const VariantArray&) = delete; template, VariantArray>>> void set(std::size_t index, T&& value) { using U = std::decay_t; static_assert(impl::TypeIndex_v < sizeof...(Types), "Type not supported by variant"); if (index >= size_and_indices_[0]) { throw std::out_of_range("Index out of range"); } destroy_at_index(index); size_and_indices_[index + 1] = impl::TypeIndex_v; using V = typename std::tuple_element, impl::MapTypes_t>::type; // std::wcout << "setting " << index << " to " << typeid(V).name() << " (" << impl::TypeIndex_v << ")" << std::endl; if constexpr (impl::is_unique_ptr::value) { new(&storage_[index]) V(new U(value)); } else { new(&storage_[index]) U(std::forward(value)); } } ~VariantArray() { free_(); } std::size_t index(std::size_t index) const noexcept { return size_and_indices_[index + 1]; } template T& get(std::size_t index) { if (!has(index)) { throw std::bad_cast(); } using V = typename std::tuple_element, impl::MapTypes_t>::type; if constexpr (impl::is_unique_ptr::value) { return **reinterpret_cast(&storage_[index]); } else { return *reinterpret_cast(&storage_[index]); } } template bool has(std::size_t index) const { return size_and_indices_[index + 1] == impl::TypeIndex::value; } template const T& get(std::size_t index) const { if (size_and_indices_[index + 1] != impl::TypeIndex::value) { // @todo this IfcException is silly. Figure out what // to do, but at the moment it is specifically caught // in various places. throw IfcParse::IfcException( "Type held at index " + std::to_string(index) + " is " + get_type_name(size_and_indices_[index + 1]) + " and not " + typeid(T).name() ); } using V = typename std::tuple_element, impl::MapTypes_t>::type; if constexpr (impl::is_unique_ptr::value) { return **reinterpret_cast(&storage_[index]); } else { return *reinterpret_cast(&storage_[index]); } } template auto apply_visitor(Visitor&& visitor, std::size_t index) const { return apply_visitor_impl(std::forward(visitor), index, std::integral_constant{}); } auto size() const { return size_and_indices_ ? size_and_indices_[0] : 0; } private: using StorageType = typename impl::make_union_from_tuple>::type; uint8_t* size_and_indices_; StorageType* storage_; void destroy_at_index(std::size_t index) { destroy_type_at_index(index, std::integral_constant{}); } void free_() { if (size_and_indices_) { for (std::size_t i = 0; i < size_and_indices_[0]; ++i) { destroy_at_index(i); } delete[] size_and_indices_; delete[] storage_; } } template void destroy_type_at_index(std::size_t index, std::integral_constant) { if (size_and_indices_[index + 1] == Index - 1) { using T = typename std::tuple_element_t>; if constexpr (!std::is_trivially_destructible::value) { reinterpret_cast(&storage_[index])->~T(); } size_and_indices_[index + 1] = sizeof...(Types); } else { destroy_type_at_index(index, std::integral_constant{}); } } void destroy_type_at_index(std::size_t, std::integral_constant) {} template auto apply_visitor_impl(Visitor&& visitor, std::size_t idx, std::integral_constant) const { if (size_and_indices_[idx + 1] == Index - 1) { using T = typename std::tuple_element_t>; if constexpr (impl::is_unique_ptr::value) { return visitor(**reinterpret_cast(&storage_[idx])); } else { return visitor(*reinterpret_cast(&storage_[idx])); } } return apply_visitor_impl(std::forward(visitor), idx, std::integral_constant{}); } template auto apply_visitor_impl(Visitor&&, std::size_t, std::integral_constant) const { throw std::runtime_error("Invalid variant index"); if constexpr (!std::is_void_v()(std::declval> &>()))>) { return decltype(std::declval()(std::declval> &>())){}; } } template const char* get_type_name_impl(size_t i) const { if constexpr (I == 0) { return ""; } else { if (i == I - 1) { return typeid(std::tuple_element_t>).name(); } else { return get_type_name_impl(i); } } } const char* get_type_name(size_t i) const { return get_type_name_impl(i); } }; #endif