/********************************************************************************
* *
* 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_(size ? new uint8_t[size + 1] : nullptr)
, 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()) {
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::TypeIndex_v, ::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 {
if (index >= size()) {
throw IfcParse::IfcException(
"Index " + std::to_string(index) + " is out of range for variant of size " + std::to_string(size())
);
}
return size_and_indices_[index + 1];
}
template
T& get(std::size_t index) {
if (index >= size()) {
throw IfcParse::IfcException(
"Index " + std::to_string(index) + " is out of range for variant of size " + std::to_string(size())
);
}
if (!has(index)) {
throw std::bad_cast();
}
using V = typename std::tuple_element<::impl::TypeIndex_v, ::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 index < size() && size_and_indices_[index + 1] == ::impl::TypeIndex::value;
}
template
const T& get(std::size_t index) const {
if (index >= size()) {
throw IfcParse::IfcException(
"Index " + std::to_string(index) + " is out of range for variant of size " + std::to_string(size())
);
}
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::TypeIndex_v, ::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 {
if (index >= size()) {
throw IfcParse::IfcException(
"Index " + std::to_string(index) + " is out of range for variant of size " + std::to_string(size())
);
}
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<::impl::MapTypes_t>::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