#ifndef RFL_VARIANT_HPP_ #define RFL_VARIANT_HPP_ #include #include #include #include #include #include #include #include "internal/element_index.hpp" #include "internal/nth_element_t.hpp" #include "internal/ptr_cast.hpp" #include "internal/variant/find_max_size.hpp" #include "internal/variant/is_alternative_type.hpp" #include "internal/variant/result_t.hpp" namespace rfl { template class Variant { static constexpr auto max_size_wrapper_ = internal::variant::find_max_size(); static constexpr unsigned long num_bytes_ = max_size_wrapper_.size_; using DataType = std::array; using IndexType = std::conditional_t::max(), std::uint8_t, std::uint16_t>; static constexpr IndexType size_ = sizeof...(AlternativeTypes); template using result_t = internal::variant::result_t; template using Index = std::integral_constant; template struct TypeWrapper {}; public: Variant() : index_(IndexType()), data_(DataType()) { using FirstAlternative = internal::nth_element_t<0, AlternativeTypes...>; move_from_type(FirstAlternative()); } Variant(const Variant& _other) : index_(IndexType()), data_(DataType()) { copy_from_other(_other); } Variant(Variant&& _other) noexcept : index_(IndexType()), data_(DataType()) { move_from_other(std::move(_other)); } template (), bool>::type = true> Variant(const T& _t) : index_(IndexType()), data_(DataType()) { copy_from_type(_t); } template (), bool>::type = true> Variant(T&& _t) noexcept : index_(IndexType()), data_(DataType()) { move_from_type(std::forward(_t)); } ~Variant() { destroy_if_necessary(); } /// Emplaces a new element into the variant. template constexpr T& emplace(Args&&... _args) { auto t = T{std::forward(_args)...}; destroy_if_necessary(); move_from_type(std::move(t)); return *internal::ptr_cast(data_.data()); } /// Emplaces a new element into the variant. template constexpr auto& emplace(Args&&... _args) { using T = internal::nth_element_t<_i, AlternativeTypes...>; return emplace(std::move(_args)...); } /// Returns the index of the element currently held. constexpr int index() const noexcept { return index_; } /// Assigns the underlying object. template (), bool>::type = true> Variant& operator=(const T& _t) { auto temp = Variant(_t); destroy_if_necessary(); move_from_other(std::move(temp)); return *this; } /// Assigns the underlying object. template (), bool>::type = true> Variant& operator=(T&& _t) noexcept { destroy_if_necessary(); move_from_type(std::forward(_t)); return *this; } /// Assigns the underlying object. Variant& operator=(const Variant& _other) { if (this == &_other) { return *this; } auto temp = Variant(_other); destroy_if_necessary(); move_from_other(std::move(temp)); return *this; } /// Assigns the underlying object. Variant& operator=(Variant&& _other) noexcept { if (this == &_other) { return *this; } destroy_if_necessary(); move_from_other(std::move(_other)); return *this; } /// Swaps the content with the other variant. void swap(Variant& _other) noexcept { if (this == &_other) { return; } auto temp = Variant(std::move(*this)); move_from_other(std::move(_other)); _other = std::move(temp); } template result_t visit(F&& _f) { using ResultType = result_t; if constexpr (std::is_same_v) { bool visited = false; do_visit_no_result(std::forward(_f), &visited, std::make_integer_sequence()); } else if constexpr (std::is_reference_v) { std::remove_reference_t* res = nullptr; do_visit_with_reference(std::forward(_f), &res, std::make_integer_sequence()); return *res; } else { auto res = std::optional(); do_visit_with_result(std::forward(_f), &res, std::make_integer_sequence()); return std::move(*res); } } template result_t visit(F&& _f) const { using ResultType = result_t; if constexpr (std::is_same_v) { bool visited = false; do_visit_no_result(std::forward(_f), &visited, std::make_integer_sequence()); } else if constexpr (std::is_reference_v) { std::remove_reference_t* res = nullptr; do_visit_with_reference(std::forward(_f), &res, std::make_integer_sequence()); return *res; } else { auto res = std::optional(); do_visit_with_result(std::forward(_f), &res, std::make_integer_sequence()); return std::move(*res); } } private: void copy_from_other(const Variant& _other) { const auto copy_one = [this](const auto& _t) { this->copy_from_type(_t); }; _other.visit(copy_one); } template void copy_from_other_type(const T& _t) { bool set = false; const auto copy_one = [&, this](const T& _t, const TypeWrapper) { if constexpr (std::is_convertible_v) { if (!set) { move_from_type(AltType(_t)); set = true; } } }; (copy_one(_t, TypeWrapper{}), ...); } template void copy_from_type(const T& _t) noexcept { using CurrentType = std::remove_cvref_t; index_ = internal::element_index...>(); new (data_.data()) CurrentType(_t); } void destroy_if_necessary() { const auto destroy_one = [](auto& _t) { using T = std::remove_cvref_t; if constexpr (std::is_destructible_v) { _t.~T(); } }; visit(destroy_one); } template void do_visit_no_result(F& _f, bool* _visited, std::integer_sequence) { auto visit_one = [this](const F& _f, bool* _visited, Index<_i>) { if (!*_visited && index_ == _i) { _f(get_alternative<_i>()); *_visited = true; } }; (visit_one(_f, _visited, Index<_is>{}), ...); } template void do_visit_no_result(F& _f, bool* _visited, std::integer_sequence) const { auto visit_one = [this](const F& _f, bool* _visited, Index<_i>) { if (!*_visited && index_ == _i) { _f(get_alternative<_i>()); *_visited = true; } }; (visit_one(_f, _visited, Index<_is>{}), ...); } template void do_visit_no_result(const F& _f, bool* _visited, std::integer_sequence) { const auto visit_one = [this](const F& _f, bool* _visited, Index<_i>) { if (!*_visited && index_ == _i) { _f(get_alternative<_i>()); *_visited = true; } }; (visit_one(_f, _visited, Index<_is>{}), ...); } template void do_visit_no_result(const F& _f, bool* _visited, std::integer_sequence) const { const auto visit_one = [this](const F& _f, bool* _visited, Index<_i>) { if (!*_visited && index_ == _i) { _f(get_alternative<_i>()); *_visited = true; } }; (visit_one(_f, _visited, Index<_is>{}), ...); } template void do_visit_with_result(F& _f, std::optional* _result, std::integer_sequence) { auto visit_one = [this](const F& _f, std::optional* _result, Index<_i>) { if (!*_result && index_ == _i) { _result->emplace(_f(get_alternative<_i>())); } }; (visit_one(_f, _result, Index<_is>{}), ...); } template void do_visit_with_result(F& _f, std::optional* _result, std::integer_sequence) const { auto visit_one = [this](const F& _f, std::optional* _result, Index<_i>) { if (!*_result && index_ == _i) { _result->emplace(_f(get_alternative<_i>())); } }; (visit_one(_f, _result, Index<_is>{}), ...); } template void do_visit_with_result(const F& _f, std::optional* _result, std::integer_sequence) { const auto visit_one = [this](const F& _f, std::optional* _result, Index<_i>) { if (!*_result && index_ == _i) { _result->emplace(_f(get_alternative<_i>())); } }; (visit_one(_f, _result, Index<_is>{}), ...); } template void do_visit_with_result(const F& _f, std::optional* _result, std::integer_sequence) const { const auto visit_one = [this](const F& _f, std::optional* _result, Index<_i>) { if (!*_result && index_ == _i) { _result->emplace(_f(get_alternative<_i>())); } }; (visit_one(_f, _result, Index<_is>{}), ...); } template void do_visit_with_reference(F& _f, ResultType** _result, std::integer_sequence) { const auto visit_one = [this](const F& _f, ResultType** _result, Index<_i>) { if (!*_result && index_ == _i) { *_result = &_f(get_alternative<_i>()); } }; (visit_one(_f, _result, Index<_is>{}), ...); } template void do_visit_with_reference(F& _f, ResultType** _result, std::integer_sequence) const { const auto visit_one = [this](const F& _f, ResultType** _result, Index<_i>) { if (!*_result && index_ == _i) { *_result = &_f(get_alternative<_i>()); } }; (visit_one(_f, _result, Index<_is>{}), ...); } template void do_visit_with_reference(const F& _f, ResultType** _result, std::integer_sequence) { const auto visit_one = [this](const F& _f, ResultType** _result, Index<_i>) { if (!*_result && index_ == _i) { *_result = &_f(get_alternative<_i>()); } }; (visit_one(_f, _result, Index<_is>{}), ...); } template void do_visit_with_reference(const F& _f, ResultType** _result, std::integer_sequence) const { const auto visit_one = [this](const F& _f, ResultType** _result, Index<_i>) { if (!*_result && index_ == _i) { *_result = &_f(get_alternative<_i>()); } }; (visit_one(_f, _result, Index<_is>{}), ...); } template auto& get_alternative() noexcept { using CurrentType = internal::nth_element_t<_i, AlternativeTypes...>; return *internal::ptr_cast(data_.data()); } template const auto& get_alternative() const noexcept { using CurrentType = internal::nth_element_t<_i, AlternativeTypes...>; return *internal::ptr_cast(data_.data()); } void move_from_other(Variant&& _other) noexcept { const auto move_one = [this](auto&& _t) { this->move_from_type(std::forward>(_t)); }; std::move(_other).visit(move_one); } template void move_from_type(T&& _t) noexcept { using CurrentType = std::remove_cvref_t; index_ = internal::element_index...>(); new (data_.data()) CurrentType(std::forward(_t)); } private: /// Index indicating which of the alternatives is currently contained in the /// variant. IndexType index_; /// The underlying data, can be any of the underlying types. alignas(AlternativeTypes...) DataType data_; }; template concept VariantBased = requires(std::decay_t v) { [](Variant const&) {}(v); }; template constexpr T* get_if(Variant* _v) noexcept { const auto get = [](auto& _v) -> T* { using Type = std::remove_cvref_t; if constexpr (std::is_same>()) { return &_v; } else { return nullptr; } }; return _v->visit(get); } template constexpr const T* get_if(const Variant* _v) noexcept { const auto get = [](const auto& _v) -> const T* { using Type = std::remove_cvref_t; if constexpr (std::is_same>()) { return &_v; } else { return nullptr; } }; return _v->visit(get); } template constexpr auto* get_if(Variant* _v) noexcept { using T = internal::nth_element_t<_i, Types...>; return get_if(_v); } template constexpr auto* get_if(const Variant* _v) noexcept { using T = internal::nth_element_t<_i, Types...>; return get_if(_v); } template constexpr T& get(Variant& _v) { auto ptr = get_if(&_v); if (!ptr) { throw std::runtime_error("Variant does not contain signified type."); } return *ptr; } template constexpr T&& get(Variant&& _v) { auto ptr = get_if(&_v); if (!ptr) { throw std::runtime_error("Variant does not contain signified type."); } return std::move(*ptr); } template constexpr const T& get(const Variant& _v) { auto ptr = get_if(&_v); if (!ptr) { throw std::runtime_error("Variant does not contain signified type."); } return *ptr; } template constexpr auto& get(Variant& _v) { auto ptr = get_if<_i>(&_v); if (!ptr) { throw std::runtime_error("Variant does not contain signified type."); } return *ptr; } template constexpr auto&& get(Variant&& _v) { auto ptr = get_if<_i>(&_v); if (!ptr) { throw std::runtime_error("Variant does not contain signified type."); } return std::move(*ptr); } template constexpr const auto& get(const Variant& _v) { auto ptr = get_if<_i>(&_v); if (!ptr) { throw std::runtime_error("Variant does not contain signified type."); } return *ptr; } template constexpr bool holds_alternative(const Variant& _v) noexcept { constexpr auto ix = internal::element_index, std::remove_cvref_t...>(); static_assert(ix != -1, "Type not supported."); return ix == _v.index(); } template struct variant_alternative; template struct variant_alternative> { using type = internal::nth_element_t; }; template using variant_alternative_t = typename variant_alternative>::type; template struct variant_size; template struct variant_size> : std::integral_constant {}; template constexpr size_t variant_size_v = variant_size>(); } // namespace rfl namespace std { template void swap(rfl::Variant& _lhs, rfl::Variant& _rhs) noexcept { _lhs.swap(_rhs); }; } // namespace std #endif