Files
IfcOpenShell/src/ifcparse/variant_array.h
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

353 lines
14 KiB
C++
Raw Normal View History

2024-08-23 20:29:07 +02:00
/********************************************************************************
* *
* 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 <http://www.gnu.org/licenses/>. *
* *
********************************************************************************/
/*
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 <iostream>
#include <stdexcept>
#include <type_traits>
#include <utility>
#include <memory>
#include <tuple>
#include <cstdint>
#include <cstring>
#include <cstddef>
#include <limits>
#include <exception>
2024-08-23 20:29:07 +02:00
namespace impl {
class storage_type_mismatch : public std::exception {
private:
std::string requested_, actual__, message_;
public:
storage_type_mismatch(const std::string& requested, const std::string& actual)
: requested_(requested), actual__(actual), message_("Requested type " + requested_ + " does not match actual type " + actual__) {}
const char* what() const noexcept override {
return message_.c_str();
}
const std::string& requested() const { return requested_; }
const std::string& actual() const { return actual__; }
};
template <typename T>
struct VariantTypeName;
2024-08-23 20:29:07 +02:00
// Trait to detect unique_ptr
template <typename...> struct is_unique_ptr : std::false_type {};
template<class T, typename... Args>
struct is_unique_ptr<std::unique_ptr<T, Args...>> : std::true_type {};
// Trait to find index of type in parameter pack considering inheritance
template <typename T, typename... Ts>
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 <typename T, typename U, typename... Ts>
struct TypeIndex<T, U, Ts...>
: std::integral_constant<std::size_t, (std::is_pointer_v<T> ? std::is_base_of_v<std::remove_pointer_t<U>, std::remove_pointer_t<T>> : std::is_same_v<T, U>) ? 0 :
(TypeIndex<T, Ts...>::value == std::numeric_limits<std::size_t>::max()
? std::numeric_limits<std::size_t>::max()
: 1 + TypeIndex<T, Ts...>::value)> {};
// Recursion termination: When the parameter pack is empty
template <typename T>
struct TypeIndex<T> : std::integral_constant<std::size_t, std::numeric_limits<std::size_t>::max()> {};
// Helper variable template
template <typename T, typename... Ts>
constexpr std::size_t TypeIndex_v = TypeIndex<T, Ts...>::value;
// Trait to determine if a type is small enough to be stored directly
template <typename T>
struct is_small_object {
static constexpr bool value = sizeof(T) <= sizeof(void*) * 2;
};
// Metafunction to transform T to unique_ptr<T> based on size
template <typename T>
struct TransformType {
using type = typename std::conditional<
is_small_object<T>::value,
T,
std::unique_ptr<T>
>::type;
};
// Helper to prepend a type to a tuple
template <typename T, typename Tuple>
struct TuplePrepend;
template <typename T, typename... Types>
struct TuplePrepend<T, std::tuple<Types...>> {
using type = std::tuple<T, Types...>;
};
// Map types based on above size transform
template <typename... Types>
struct MapTypes;
template <typename FirstType, typename... RestTypes>
struct MapTypes<FirstType, RestTypes...> {
using type = typename TuplePrepend<
typename TransformType<FirstType>::type,
typename MapTypes<RestTypes...>::type
>::type;
};
template <>
struct MapTypes<> {
using type = std::tuple<>;
};
template <typename... Types>
using MapTypes_t = typename MapTypes<Types...>::type;
// Create aligned_union from paramater pack stored in tuple for storage in variant
template <typename T>
struct make_union_from_tuple {};
template <typename... Args>
struct make_union_from_tuple<std::tuple<Args...>> {
using type = typename std::aligned_union<0, Args...>::type;
};
}
template<typename... Types>
2026-03-31 15:32:36 +02:00
class variant_array {
2024-08-23 20:29:07 +02:00
public:
2024-08-25 09:53:41 +02:00
using TypesTuple = ::impl::MapTypes_t<Types...>;
2024-08-23 20:29:07 +02:00
2026-03-31 15:32:36 +02:00
variant_array(size_t size)
2025-04-30 20:43:43 +02:00
: size_and_indices_(size ? new uint8_t[size + 1] : nullptr)
2024-08-23 20:29:07 +02:00
, storage_(size ? new StorageType[size] : nullptr)
{
if (size) {
2024-08-25 09:53:41 +02:00
size_and_indices_[0] = (uint8_t)size;
2024-08-23 20:29:07 +02:00
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<Types...>>::type{});
}
}
}
2026-03-31 15:32:36 +02:00
variant_array(variant_array&& other) noexcept
2024-08-23 20:29:07 +02:00
: size_and_indices_(other.size_and_indices_)
, storage_(other.storage_)
{
other.size_and_indices_ = nullptr;
other.storage_ = nullptr;
}
2026-03-31 15:32:36 +02:00
variant_array& operator=(variant_array&& other) noexcept {
2024-08-23 20:29:07 +02:00
if (this != &other) {
free_();
size_and_indices_ = other.size_and_indices_;
storage_ = other.storage_;
other.size_and_indices_ = nullptr;
other.storage_ = nullptr;
}
return *this;
}
2026-03-31 18:23:13 +02:00
variant_array(const variant_array& other) = delete;
variant_array(const variant_array&& other) = delete;
variant_array& operator= (const variant_array& other) = delete;
2024-08-23 20:29:07 +02:00
2026-03-31 15:32:36 +02:00
template<typename T, typename = std::enable_if_t<!std::is_same_v<std::decay_t<T>, variant_array>>>
2024-08-23 20:29:07 +02:00
void set(std::size_t index, T&& value) {
using U = std::decay_t<T>;
2024-08-25 09:53:41 +02:00
static_assert(::impl::TypeIndex_v<U, Types...> < sizeof...(Types), "Type not supported by variant");
2025-04-30 20:43:43 +02:00
if (index >= size()) {
throw std::out_of_range("Index " + std::to_string(index) + " is out of range for storage of size " + std::to_string(size()));
2024-08-23 20:29:07 +02:00
}
destroy_at_index(index);
2024-08-25 09:53:41 +02:00
size_and_indices_[index + 1] = ::impl::TypeIndex_v<U, Types...>;
using V = typename std::tuple_element<::impl::TypeIndex_v<U, Types...>, ::impl::MapTypes_t<Types... >>::type;
if constexpr (::impl::is_unique_ptr<V>::value) {
2024-08-23 20:29:07 +02:00
new(&storage_[index]) V(new U(value));
} else {
new(&storage_[index]) U(std::forward<T>(value));
}
}
2026-03-31 15:32:36 +02:00
~variant_array() {
2024-08-23 20:29:07 +02:00
free_();
}
std::size_t index(std::size_t index) const {
2025-04-30 20:43:43 +02:00
if (index >= size()) {
throw std::out_of_range(
"Index " + std::to_string(index) + " is out of range for storage of size " + std::to_string(size())
2025-04-30 20:43:43 +02:00
);
}
2024-08-23 20:29:07 +02:00
return size_and_indices_[index + 1];
}
template<typename T>
T& get(std::size_t index) {
2025-04-30 20:43:43 +02:00
if (index >= size()) {
throw std::out_of_range(
"Index " + std::to_string(index) + " is out of range for storage of size " + std::to_string(size())
2025-04-30 20:43:43 +02:00
);
}
2024-08-23 20:29:07 +02:00
if (!has<T>(index)) {
throw std::bad_cast();
}
2024-08-25 09:53:41 +02:00
using V = typename std::tuple_element<::impl::TypeIndex_v<T, Types...>, ::impl::MapTypes_t<Types... >>::type;
if constexpr (::impl::is_unique_ptr<V>::value) {
2024-08-23 20:29:07 +02:00
return **reinterpret_cast<V*>(&storage_[index]);
} else {
return *reinterpret_cast<V*>(&storage_[index]);
}
}
template<typename T>
bool has(std::size_t index) const {
2025-04-30 20:43:43 +02:00
return index < size() && size_and_indices_[index + 1] == ::impl::TypeIndex<T, Types...>::value;
2024-08-23 20:29:07 +02:00
}
template<typename T>
const T& get(std::size_t index) const {
2025-04-30 20:43:43 +02:00
if (index >= size()) {
throw std::out_of_range(
"Index " + std::to_string(index) + " is out of range for storage of size " + std::to_string(size())
2025-04-30 20:43:43 +02:00
);
}
2024-08-25 09:53:41 +02:00
if (size_and_indices_[index + 1] != ::impl::TypeIndex<T, Types...>::value) {
2026-03-31 15:32:36 +02:00
// @todo this exception is silly. Figure out what
2024-08-23 20:29:07 +02:00
// to do, but at the moment it is specifically caught
// in various places.
throw impl::storage_type_mismatch(
::impl::VariantTypeName<T>::get(), get_type_name(size_and_indices_[index + 1])
2024-08-23 20:29:07 +02:00
);
}
2024-08-25 09:53:41 +02:00
using V = typename std::tuple_element<::impl::TypeIndex_v<T, Types...>, ::impl::MapTypes_t<Types... >>::type;
if constexpr (::impl::is_unique_ptr<V>::value) {
2024-08-23 20:29:07 +02:00
return **reinterpret_cast<const V*>(&storage_[index]);
} else {
return *reinterpret_cast<const V*>(&storage_[index]);
}
}
template<typename Visitor>
auto apply_visitor(Visitor&& visitor, std::size_t index) const {
2025-04-30 20:43:43 +02:00
if (index >= size()) {
throw std::out_of_range(
"Index " + std::to_string(index) + " is out of range for storage of size " + std::to_string(size())
2025-04-30 20:43:43 +02:00
);
}
2024-08-23 20:29:07 +02:00
return apply_visitor_impl(std::forward<Visitor>(visitor), index, std::integral_constant<std::size_t, sizeof...(Types)>{});
}
size_t size() const {
2024-08-23 20:29:07 +02:00
return size_and_indices_ ? size_and_indices_[0] : 0;
}
private:
2024-08-25 09:53:41 +02:00
using StorageType = typename ::impl::make_union_from_tuple<::impl::MapTypes_t<Types...>>::type;
2024-08-23 20:29:07 +02:00
uint8_t* size_and_indices_;
StorageType* storage_;
void destroy_at_index(std::size_t index) {
destroy_type_at_index(index, std::integral_constant<std::size_t, sizeof...(Types)>{});
}
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<std::size_t Index>
void destroy_type_at_index(std::size_t index, std::integral_constant<std::size_t, Index>) {
if (size_and_indices_[index + 1] == Index - 1) {
2024-08-25 09:53:41 +02:00
using T = typename std::tuple_element_t<Index - 1, ::impl::MapTypes_t<Types...>>;
2024-08-23 20:29:07 +02:00
if constexpr (!std::is_trivially_destructible<T>::value) {
reinterpret_cast<T*>(&storage_[index])->~T();
}
size_and_indices_[index + 1] = sizeof...(Types);
} else {
destroy_type_at_index(index, std::integral_constant<std::size_t, Index - 1>{});
}
}
2026-03-31 18:23:13 +02:00
void destroy_type_at_index(std::size_t index, std::integral_constant<std::size_t, 0>) {
static_cast<void>(index);
}
2024-08-23 20:29:07 +02:00
template<typename Visitor, std::size_t Index>
2026-03-31 18:23:13 +02:00
auto apply_visitor_impl(Visitor&& visitor, std::size_t index, std::integral_constant<std::size_t, Index>) const {
if (size_and_indices_[index + 1] == Index - 1) {
2024-08-25 09:53:41 +02:00
using T = typename std::tuple_element_t<Index - 1, ::impl::MapTypes_t<Types...>>;
if constexpr (::impl::is_unique_ptr<T>::value) {
2026-03-31 18:23:13 +02:00
return visitor(**reinterpret_cast<T*>(&storage_[index]));
2024-08-23 20:29:07 +02:00
} else {
2026-03-31 18:23:13 +02:00
return visitor(*reinterpret_cast<T*>(&storage_[index]));
2024-08-23 20:29:07 +02:00
}
}
2026-03-31 18:23:13 +02:00
return apply_visitor_impl(std::forward<Visitor>(visitor), index, std::integral_constant<std::size_t, Index - 1>{});
2024-08-23 20:29:07 +02:00
}
template<typename Visitor>
2026-03-31 18:23:13 +02:00
auto apply_visitor_impl(Visitor&& visitor, std::size_t index, std::integral_constant<std::size_t, 0>) const {
static_cast<void>(visitor);
static_cast<void>(index);
2024-08-23 20:29:07 +02:00
throw std::runtime_error("Invalid variant index");
2024-08-25 09:53:41 +02:00
if constexpr (!std::is_void_v<decltype(std::declval<Visitor>()(std::declval<typename std::tuple_element_t<0, ::impl::MapTypes_t<Types...>> &>()))>) {
return decltype(std::declval<Visitor>()(std::declval<typename std::tuple_element_t<0, ::impl::MapTypes_t<Types...>> &>())){};
2024-08-23 20:29:07 +02:00
}
}
template <size_t I>
std::string get_type_name_impl(size_t type_index) const {
2024-08-23 20:29:07 +02:00
if constexpr (I == 0) {
return "";
} else {
2026-03-31 18:23:13 +02:00
if (type_index == I - 1) {
return ::impl::VariantTypeName<std::tuple_element_t<I - 1, std::tuple<Types...>>>::get();
2024-08-23 20:29:07 +02:00
} else {
2026-03-31 18:23:13 +02:00
return get_type_name_impl<I - 1>(type_index);
2024-08-23 20:29:07 +02:00
}
}
}
std::string get_type_name(size_t type_index) const {
2026-03-31 18:23:13 +02:00
return get_type_name_impl<sizeof...(Types)>(type_index);
2024-08-23 20:29:07 +02:00
}
};
#endif