#ifndef STAR_VARIANT_HPP #define STAR_VARIANT_HPP #include #include #include #include "StarAlgorithm.hpp" #include "StarMaybe.hpp" namespace Star { STAR_EXCEPTION(BadVariantCast, StarException); STAR_EXCEPTION(BadVariantType, StarException); typedef uint8_t VariantTypeIndex; VariantTypeIndex const InvalidVariantType = 255; namespace detail { template struct HasType; template struct HasType : std::false_type {}; template struct HasType { static constexpr bool value = std::is_same::value || HasType::value; }; template struct IsNothrowMoveConstructible; template <> struct IsNothrowMoveConstructible<> : std::true_type {}; template struct IsNothrowMoveConstructible { static constexpr bool value = std::is_nothrow_move_constructible::value && IsNothrowMoveConstructible::value; }; template struct IsNothrowMoveAssignable; template <> struct IsNothrowMoveAssignable<> : std::true_type {}; template struct IsNothrowMoveAssignable { static constexpr bool value = std::is_nothrow_move_assignable::value && IsNothrowMoveAssignable::value; }; } // Stack based variant type container that can be inhabited by one of a limited // number of types. template class Variant { public: template using ValidateType = typename std::enable_if::value, void>::type; template > static constexpr VariantTypeIndex typeIndexOf(); // If the first type has a default constructor, constructs an Variant which // contains a default constructed value of that type. Variant(); template > Variant(T const& x); template > Variant(T&& x); template , typename... Args, typename std::enable_if< std::is_constructible::value, int >::type = 0 > Variant(std::in_place_type_t, Args&&... args) { new (&m_buffer) T(std::forward(args)...); m_typeIndex = TypeIndex::value; } template , typename... Args, typename std::enable_if< std::is_constructible&, Args...>::value, int >::type = 0 > Variant(std::in_place_type_t, std::initializer_list il, Args&&... args) { new (&m_buffer) T(il, std::forward(args)...); m_typeIndex = TypeIndex::value; } Variant(Variant const& x); Variant(Variant&& x) noexcept(detail::IsNothrowMoveConstructible::value); ~Variant(); // Implementations of operator= may invalidate the Variant if the copy or // move constructor of the assigned value throws. Variant& operator=(Variant const& x); Variant& operator=(Variant&& x) noexcept(detail::IsNothrowMoveAssignable::value); template > Variant& operator=(T const& x); template > Variant& operator=(T&& x); // Returns true if this Variant contains the given type. template > bool is() const; // get throws BadVariantCast on bad casts template > T const& get() const; template > T& get(); template > Maybe maybe() const; // ptr() does not throw if this Variant does not hold the given type, instead // simply returns nullptr. template > T const* ptr() const; template > T* ptr(); // Calls the given function with the type currently being held, and returns // the value returned by that function. Will throw if this Variant has been // invalidated. template decltype(auto) call(Function&& function); template decltype(auto) call(Function&& function) const; // Returns an index for the held type, which can be passed into makeType to // make this Variant hold a specific type. Returns InvalidVariantType if // invalidated. VariantTypeIndex typeIndex() const; // Make this Variant hold a new default constructed type of the given type // index. Can only be used if every alternative type has a default // constructor. Throws if given an out of range type index or // InvalidVariantType. void makeType(VariantTypeIndex typeIndex); // True if this Variant has been invalidated. If the copy or move // constructor of a type throws an exception during assignment, there is no // *good* way to ensure that the Variant has a valid type, so it may become // invalidated. It is not possible to directly construct an invalidated // Variant. bool invalid() const; // Requires that every type included in this Variant has operator== bool operator==(Variant const& x) const; bool operator!=(Variant const& x) const; // Requires that every type included in this Variant has operator< bool operator<(Variant const& x) const; template > bool operator==(T const& x) const; template > bool operator!=(T const& x) const; template > bool operator<(T const& x) const; private: template struct LookupTypeIndex; template struct LookupTypeIndex { static VariantTypeIndex const value = InvalidVariantType; }; template struct LookupTypeIndex { static VariantTypeIndex const value = std::is_same::value ? Index : LookupTypeIndex::value; }; template struct TypeIndex { static VariantTypeIndex const value = LookupTypeIndex::value; }; void destruct(); template void assign(T&& x); template decltype(auto) doCall(Function&& function); template decltype(auto) doCall(Function&& function); template decltype(auto) doCall(Function&& function) const; template decltype(auto) doCall(Function&& function) const; template void doMakeType(VariantTypeIndex); template void doMakeType(VariantTypeIndex typeIndex); typename std::aligned_union<0, FirstType, RestTypes...>::type m_buffer; VariantTypeIndex m_typeIndex = InvalidVariantType; }; // A version of Variant that has always has a default "empty" state, useful // when there is no good default type for a Variant but it needs to be default // constructed, and is slightly more convenient than Maybe>. template class MVariant { public: template using ValidateType = typename std::enable_if::value, void>::type; template > static constexpr VariantTypeIndex typeIndexOf(); MVariant(); MVariant(MVariant const& x); MVariant(MVariant&& x); template > MVariant(T const& x); template > MVariant(T&& x); MVariant(Variant const& x); MVariant(Variant&& x); ~MVariant(); // MVariant::operator= will never invalidate the MVariant, instead it will // just become empty. MVariant& operator=(MVariant const& x); MVariant& operator=(MVariant&& x); template > MVariant& operator=(T const& x); template > MVariant& operator=(T&& x); MVariant& operator=(Variant const& x); MVariant& operator=(Variant&& x); // Requires that every type included in this MVariant has operator== bool operator==(MVariant const& x) const; bool operator!=(MVariant const& x) const; // Requires that every type included in this MVariant has operator< bool operator<(MVariant const& x) const; template > bool operator==(T const& x) const; template > bool operator!=(T const& x) const; template > bool operator<(T const& x) const; // get throws BadVariantCast on bad casts template > T const& get() const; template > T& get(); // maybe() and ptr() do not throw if this MVariant does not hold the given // type, instead simply returns Nothing / nullptr. template > Maybe maybe() const; template > T const* ptr() const; template > T* ptr(); template > bool is() const; // Takes the given value out and leaves this empty template > T take(); // Returns a Variant of all the allowed types if non-empty, throws // BadVariantCast if empty. Variant value() const; // Moves the contents of this MVariant into the given Variant if non-empty, // throws BadVariantCast if empty. Variant takeValue(); bool empty() const; void reset(); // Equivalent to !empty() explicit operator bool() const; // If this MVariant holds a type, calls the given function with the type // being held. If nothing is currently held, the function is not called. template void call(Function&& function); template void call(Function&& function) const; // Returns an index for the held type, which can be passed into makeType to // make this MVariant hold a specific type. Types are always indexed in the // order they are specified starting from 1. A type index of 0 indicates an // empty MVariant. VariantTypeIndex typeIndex() const; // Make this MVariant hold a new default constructed type of the given type // index. Can only be used if every alternative type has a default // constructor. void makeType(VariantTypeIndex typeIndex); private: struct MVariantEmpty { bool operator==(MVariantEmpty const& rhs) const; bool operator<(MVariantEmpty const& rhs) const; }; template struct RefCaller { Function&& function; RefCaller(Function&& function); void operator()(MVariantEmpty& empty); template void operator()(T& t); }; template struct ConstRefCaller { Function&& function; ConstRefCaller(Function&& function); void operator()(MVariantEmpty const& empty); template void operator()(T const& t); }; Variant m_variant; }; template template constexpr VariantTypeIndex Variant::typeIndexOf() { return TypeIndex::value; } template Variant::Variant() : Variant(FirstType()) {} template template Variant::Variant(T const& x) { assign(x); } template template Variant::Variant(T&& x) { assign(std::forward(x)); } template Variant::Variant(Variant const& x) { x.call([&](auto const& t) { assign(t); }); } template Variant::Variant(Variant&& x) noexcept(detail::IsNothrowMoveConstructible::value) { x.call([&](auto& t) { assign(std::move(t)); }); } template Variant::~Variant() { destruct(); } template Variant& Variant::operator=(Variant const& x) { if (&x == this) return *this; x.call([&](auto const& t) { assign(t); }); return *this; } template Variant& Variant::operator=(Variant&& x) noexcept(detail::IsNothrowMoveAssignable::value) { if (&x == this) return *this; x.call([&](auto& t) { assign(std::move(t)); }); return *this; } template template Variant& Variant::operator=(T const& x) { assign(x); return *this; } template template Variant& Variant::operator=(T&& x) { assign(std::forward(x)); return *this; } template template T const& Variant::get() const { if (!is()) throw BadVariantCast(); return *(T*)(&m_buffer); } template template T& Variant::get() { if (!is()) throw BadVariantCast(); return *(T*)(&m_buffer); } template template Maybe Variant::maybe() const { if (!is()) return {}; return *(T*)(&m_buffer); } template template T const* Variant::ptr() const { if (!is()) return nullptr; return (T*)(&m_buffer); } template template T* Variant::ptr() { if (!is()) return nullptr; return (T*)(&m_buffer); } template template bool Variant::is() const { return m_typeIndex == TypeIndex::value; } template template decltype(auto) Variant::call(Function&& function) { return doCall(std::forward(function)); } template template decltype(auto) Variant::call(Function&& function) const { return doCall(std::forward(function)); } template VariantTypeIndex Variant::typeIndex() const { return m_typeIndex; } template void Variant::makeType(VariantTypeIndex typeIndex) { return doMakeType(typeIndex); } template bool Variant::invalid() const { return m_typeIndex == InvalidVariantType; } template bool Variant::operator==(Variant const& x) const { if (this == &x) { return true; } else if (typeIndex() != x.typeIndex()) { return false; } else { return call([&x](auto const& t) { typedef typename std::decay::type T; return t == x.template get(); }); } } template bool Variant::operator!=(Variant const& x) const { return !operator==(x); } template bool Variant::operator<(Variant const& x) const { if (this == &x) { return false; } else { auto sti = typeIndex(); auto xti = x.typeIndex(); if (sti != xti) { return sti < xti; } else { return call([&x](auto const& t) { typedef typename std::decay::type T; return t < x.template get(); }); } } } template template bool Variant::operator==(T const& x) const { if (auto p = ptr()) return *p == x; return false; } template template bool Variant::operator!=(T const& x) const { return !operator==(x); } template template bool Variant::operator<(T const& x) const { if (auto p = ptr()) return *p == x; return m_typeIndex < TypeIndex::value; } template void Variant::destruct() { if (m_typeIndex != InvalidVariantType) { try { call([](auto& t) { typedef typename std::decay::type T; t.~T(); }); m_typeIndex = InvalidVariantType; } catch (...) { m_typeIndex = InvalidVariantType; throw; } } } template template void Variant::assign(T&& x) { typedef typename std::decay::type AssignType; if (auto p = ptr()) { *p = std::forward(x); } else { destruct(); new (&m_buffer) AssignType(std::forward(x)); m_typeIndex = TypeIndex::value; } } template template decltype(auto) Variant::doCall(Function&& function) { if (T* p = ptr()) return function(*p); else throw BadVariantType(); } template template decltype(auto) Variant::doCall(Function&& function) { if (T1* p = ptr()) return function(*p); else return doCall(std::forward(function)); } template template decltype(auto) Variant::doCall(Function&& function) const { if (T const* p = ptr()) return function(*p); else throw BadVariantType(); } template template decltype(auto) Variant::doCall(Function&& function) const { if (T1 const* p = ptr()) return function(*p); else return doCall(std::forward(function)); } template template void Variant::doMakeType(VariantTypeIndex typeIndex) { if (typeIndex == 0) *this = First(); else throw BadVariantType(); } template template void Variant::doMakeType(VariantTypeIndex typeIndex) { if (typeIndex == 0) *this = First(); else return doMakeType(typeIndex - 1); } template template constexpr VariantTypeIndex MVariant::typeIndexOf() { return Variant::template typeIndexOf(); } template MVariant::MVariant() {} template MVariant::MVariant(MVariant const& x) : m_variant(x.m_variant) {} template MVariant::MVariant(MVariant&& x) { m_variant = std::move(x.m_variant); x.m_variant = MVariantEmpty(); } template MVariant::MVariant(Variant const& x) { operator=(x); } template MVariant::MVariant(Variant&& x) { operator=(std::move(x)); } template template MVariant::MVariant(T const& x) : m_variant(x) {} template template MVariant::MVariant(T&& x) : m_variant(std::forward(x)) {} template MVariant::~MVariant() {} template MVariant& MVariant::operator=(MVariant const& x) { try { m_variant = x.m_variant; } catch (...) { if (m_variant.invalid()) m_variant = MVariantEmpty(); throw; } return *this; } template MVariant& MVariant::operator=(MVariant&& x) { try { m_variant = std::move(x.m_variant); } catch (...) { if (m_variant.invalid()) m_variant = MVariantEmpty(); throw; } return *this; } template template MVariant& MVariant::operator=(T const& x) { try { m_variant = x; } catch (...) { if (m_variant.invalid()) m_variant = MVariantEmpty(); throw; } return *this; } template template MVariant& MVariant::operator=(T&& x) { try { m_variant = std::forward(x); } catch (...) { if (m_variant.invalid()) m_variant = MVariantEmpty(); throw; } return *this; } template MVariant& MVariant::operator=(Variant const& x) { x.call([this](auto const& t) { *this = t; }); return *this; } template MVariant& MVariant::operator=(Variant&& x) { x.call([this](auto& t) { *this = std::move(t); }); return *this; } template bool MVariant::operator==(MVariant const& x) const { return m_variant == x.m_variant; } template bool MVariant::operator!=(MVariant const& x) const { return m_variant != x.m_variant; } template bool MVariant::operator<(MVariant const& x) const { return m_variant < x.m_variant; } template template bool MVariant::operator==(T const& x) const { return m_variant == x; } template template bool MVariant::operator!=(T const& x) const { return m_variant != x; } template template bool MVariant::operator<(T const& x) const { return m_variant < x; } template template T const& MVariant::get() const { return m_variant.template get(); } template template T& MVariant::get() { return m_variant.template get(); } template template Maybe MVariant::maybe() const { return m_variant.template maybe(); } template template T const* MVariant::ptr() const { return m_variant.template ptr(); } template template T* MVariant::ptr() { return m_variant.template ptr(); } template template bool MVariant::is() const { return m_variant.template is(); } template template T MVariant::take() { T t = std::move(m_variant.template get()); m_variant = MVariantEmpty(); return t; } template Variant MVariant::value() const { if (empty()) throw BadVariantCast(); Variant r; call([&r](auto const& v) { r = v; }); return r; } template Variant MVariant::takeValue() { if (empty()) throw BadVariantCast(); Variant r; call([&r](auto& v) { r = std::move(v); }); m_variant = MVariantEmpty(); return r; } template bool MVariant::empty() const { return m_variant.template is(); } template void MVariant::reset() { m_variant = MVariantEmpty(); } template MVariant::operator bool() const { return !empty(); } template template void MVariant::call(Function&& function) { m_variant.call(RefCaller(std::forward(function))); } template template void MVariant::call(Function&& function) const { m_variant.call(ConstRefCaller(std::forward(function))); } template VariantTypeIndex MVariant::typeIndex() const { return m_variant.typeIndex(); } template void MVariant::makeType(VariantTypeIndex typeIndex) { m_variant.makeType(typeIndex); } template bool MVariant::MVariantEmpty::operator==(MVariantEmpty const&) const { return true; } template bool MVariant::MVariantEmpty::operator<(MVariantEmpty const&) const { return false; } template template MVariant::RefCaller::RefCaller(Function&& function) : function(std::forward(function)) {} template template void MVariant::RefCaller::operator()(MVariantEmpty&) {} template template template void MVariant::RefCaller::operator()(T& t) { function(t); } template template MVariant::ConstRefCaller::ConstRefCaller(Function&& function) : function(std::forward(function)) {} template template void MVariant::ConstRefCaller::operator()(MVariantEmpty const&) {} template template template void MVariant::ConstRefCaller::operator()(T const& t) { function(t); } } template struct fmt::formatter> : ostream_formatter {}; #endif