#pragma once #include "gp_config.hpp" #include #include #include "gp/utils/allocators/dummy.hpp" #include "gp/exception.hpp" #include "gp/functional/function.hpp" #include #include // TODO: Implement the allocating flavour of variant namespace gp{ /** * @brief A form of variant that expect only classes whose size is strictly defined at compile time * * @tparam T The list of types accepted by the variant */ template class fixed_variant final { std::size_t index = std::numeric_limits::max(); alignas(gp::max(alignof(T)...)) char data[max_size()]; gp::function cpytor = {[](void*, void*){}, nullopt}; gp::function mvtor = {[](void*, void*){}, nullopt}; gp::function dtor = {[](void*){}, nullopt}; /** * This is kinda bad, but stems from my inability to make gp::function work properly with constness */ bool(*cmptor)(const void*, const void*) = +[](const void*, const void*) -> bool {return false;}; static_assert(all_of_fixed_size::value, "not fixed"); public: fixed_variant() : fixed_variant(typename first_of::type{}) {} /** * @brief Construct a new fixed variant from an object that is legal in it * * @tparam U A type that belongs in the list of types the variant supports * @param value The copied value */ template,T...>::value,int> = 0> fixed_variant(U& value) : index{r_index_of, T...>::value} { using actual = gp::remove_cvref_t; dtor = gp::function([](void* thing){ ((actual*)thing)->~actual(); }, nullopt); cpytor = gp::function([](void* src, void* dest){ new(dest) actual(*(actual*)src); }, nullopt); mvtor = gp::function([](void* src, void* dest){ new(dest) actual(gp::move(*(actual*)src)); }, nullopt); cpytor((char*)&value, data); cmptor = +[](const void* lhs, const void* rhs) { return *reinterpret_cast(lhs) == *reinterpret_cast(rhs); }; } static_assert(list_contains_class::value, "list_contains_class doesn't work properly"); static_assert(list_contains_class::value, "list_contains_class doesn't work properly"); static_assert(list_contains_class::value, "list_contains_class doesn't work properly"); static_assert(list_contains_class::value, "list_contains_class doesn't work properly"); static_assert(!list_contains_class::value, "list_contains_class doesn't work properly"); /** * @brief Construct a new fixed variant from an object that is legal in it by moving said object * * @tparam U A type that belongs in the list of types the variant supports * @param value The moved value */ template,T...>::value,int> = 0> fixed_variant(U&& value) : index{r_index_of, T...>::value} { using actual = gp::remove_cvref_t; dtor = gp::function([](void* thing){ ((actual*)thing)->~actual(); }, nullopt); cpytor = gp::function([](void* src, void* dest){ new(dest) actual(*(actual*)src); }, nullopt); mvtor = gp::function([](void* src, void* dest){ new(dest) actual(gp::move(*(actual*)src)); }, nullopt); mvtor((char*)&value, data); cmptor = +[](const void* lhs, const void* rhs) { return *reinterpret_cast(lhs) == *reinterpret_cast(rhs); }; } fixed_variant(const fixed_variant& oth) : index{oth.index} , dtor{oth.dtor} , cpytor{oth.cpytor} , mvtor{oth.mvtor} , cmptor{oth.cmptor} { cpytor((char*)oth.data, (char*)data); } fixed_variant(fixed_variant& oth) : index{oth.index} , dtor{oth.dtor} , cpytor{oth.cpytor} , mvtor{oth.mvtor} , cmptor{oth.cmptor} { cpytor(oth.data, data); } fixed_variant(fixed_variant&& oth) : index{oth.index} , dtor{oth.dtor} , cpytor{oth.cpytor} , mvtor{oth.mvtor} , cmptor{oth.cmptor} { oth.index = std::numeric_limits::max(); mvtor(oth.data, data); } /** * @brief Gives an alternative (value usable in a switch statement) representing the given type * * @tparam U the type to match against * @return constexpr size_t a value that can be used in the case part of a switch case statement * @see type() */ template constexpr static size_t alt() { return r_index_of::value; } /** * @brief Gives the type as can be matched in a switch statement using alternatives * * @return size_t a value that can be used in the switch part of a switch case statement * @see alt() */ size_t type() const { return index; } void operator=(fixed_variant& value) { if(index != std::numeric_limits::max()) { dtor((void*)data); } index = value.index; cpytor = value.cpytor; dtor = value.dtor; mvtor = value.mvtor; cpytor(value.data, data); cmptor = value.cmptor; } void operator=(fixed_variant&& value) { if(index != std::numeric_limits::max()) { dtor((void*)data); } dtor = value.dtor; cpytor = value.cpytor; mvtor = value.mvtor; index = value.index; value.index = std::numeric_limits::max(); mvtor(value.data, data); cmptor = value.cmptor; } template::value,int>::type> void operator=(U& value) { if(index != std::numeric_limits::max()) { dtor((void*)data); } index = r_index_of, T...>::value; new(data) U(value); dtor = gp::function([](void* thing){((U*)thing)->~U();}, nullopt); cpytor = gp::function([](void* src, void* dest){new(dest) U(*(U*)src);}, nullopt); mvtor = gp::function([](void* src, void* dest){new(dest) U(gp::move(*(U*)src));}, nullopt); cmptor = +[](const void* lhs, const void* rhs) { return *reinterpret_cast(lhs) == *reinterpret_cast(rhs); }; } template::value,int>::type> void operator=(U&& value) { if(index != std::numeric_limits::max()) { dtor((void*)data); } new(data) U(gp::move(value)); index = r_index_of, T...>::value; dtor = gp::function([](void* thing){((U*)thing)->~U();}, nullopt); cpytor = gp::function([](void* src, void* dest){new(dest) U(*(U*)src);}, nullopt); mvtor = gp::function([](void* src, void* dest){new(dest) U(gp::move(*(U*)src));}, nullopt); cmptor = +[](const void* lhs, const void* rhs) { return *reinterpret_cast(lhs) == *reinterpret_cast(rhs); }; } ~fixed_variant() { if(index != std::numeric_limits::max() && dtor.ready()) { dtor((void*)data); index = std::numeric_limits::max(); } } /** * @brief Brutally decay the variant into the given type. Will throw (@see bad_variant_access) if exceptions are enabled, else behaviour is undefined. * * @tparam U the type to decay towards * @return constexpr U& a decayed reference to the variant */ template constexpr U& value() { if constexpr (gp_config::has_exceptions) { if(r_index_of, T...>::value != index) { throw bad_variant_access{}; } } return *reinterpret_cast(data); } /** * @brief Brutally decay the variant into the given type. Will throw (@see bad_variant_access) if exceptions are enabled, else behaviour is undefined. * * @tparam U the type to decay towards * @return constexpr U& a decayed reference to the variant */ template constexpr const U& value() const { if constexpr (gp_config::has_exceptions) { if(r_index_of, T...>::value != index) { throw bad_variant_access{}; } } return *reinterpret_cast(data); } /** * @brief Tests if the variant is of a particular type. * * @tparam U the type to match against * @return true if the types match * @return false if the types don't match */ template constexpr bool is_a() const { if(r_index_of, T...>::value == index) { return true; } else { return false; } } bool operator==(const fixed_variant& rhs) const { if(index != rhs.index) { return false; } // TODO: find a way to remove those const casts, the underlying things also use const so it is safe but kind ew return cmptor(reinterpret_cast(const_cast(this)), reinterpret_cast(const_cast(&rhs))); } }; }