/* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * Copyright 2018 Danny Robson */ #pragma once #include #include "../cast.hpp" #include "../maths.hpp" #include "../tuple/type.hpp" #include /////////////////////////////////////////////////////////////////////////////// namespace cruft { /// A tagged union of trivial types with a user-defined 'tag' type. /// /// This class supports far less functionality than does std::variant, but /// it simultaneously enforces far less complexity and allows for a known /// tag type. If you need something more flexible: /// don't extend this, just use std::variant. /// /// Storage is through a simple array of bytes (with suitable alignment). /// /// It should be impossible to construct an object in an uninitialised /// state. /// /// Requirements: /// * All member types must have a static constexpr 'tag' member of /// identical type that uniquely maps to a member type. /// /// \tparam ValueT A pack of possible member types to store. template class tagged { public: using tuple_t = std::tuple; using first_t = cruft::tuple::type::nth_t; using tag_t = std::decay_t; /// All child types should have identical tag types static_assert ((std::is_same_v> && ...)); /// All child types must be trivial, given we provide limited /// construction and destruction support. static_assert ((std::is_trivially_copyable_v && ...)); static_assert ((std::is_trivially_destructible_v && ...)); //--------------------------------------------------------------------- template requires (cruft::concepts::same_as, ValueT> || ...) tagged (InitialT &&initial) { set (std::forward (initial)); } ~tagged () = default; tagged (tagged const&) = default; tagged (tagged &&) = default; tagged& operator= (tagged const&) = default; tagged& operator= (tagged &) = default; tagged& operator= (tagged &&) = default; //--------------------------------------------------------------------- template NextT& operator= (NextT &&next) { return set (std::forward (next)); } ///-------------------------------------------------------------------- /// Return the type code associated with the stored value. auto tag (void) const { return m_tag; } auto tag (void) { return m_tag; } /// Returns true if the contained type is the same as the type /// specified in the template parameter. template bool is (void) const noexcept { return QueryT::tag == m_tag; } ///-------------------------------------------------------------------- /// Return a reference to a stored value of known type. /// /// Requesting a reference to an incorrect type has undefined /// behaviour, but trigger should an assertion if the build has been /// configured with them enabled. template InnerT& get (void)& { CHECK (InnerT::tag == m_tag); return *cruft::cast::alignment*> (m_data + 0); } template InnerT const& get (void) const& { CHECK (InnerT::tag == m_tag); return *cruft::cast::alignment const*> (m_data + 0); } /// Set the inner member to a supplied value and store the associated /// type code. template InnerT& set (InnerT &&inner)& { m_tag = inner.tag; return *cruft::cast::alignment*> (m_data + 0) = std::forward (inner); } private: /// The tag value for the currently active type tag_t m_tag; /// The storage area for all desired types. alignas (alignof (ValueT)...) std::byte m_data[ cruft::max (sizeof (ValueT)...) ]; }; } namespace std { template struct tuple_size<::cruft::tagged> : std::integral_constant<::std::size_t, sizeof...(ComponentT)> { ; }; template struct tuple_element< I, ::cruft::tagged > : ::std::tuple_element< I, std::tuple > { ; }; } namespace cruft { namespace detail { // If the tagged object matches the IndexV'th type in ComponentsT then // invoke visitor with the object as the argument. Else, advance to // the next index and try with that type. // // We assume that outside callers only call this function with an // IndexV of 0 (so that we have assurances that every type is visited). template < std::size_t IndexV, typename VisitorT, typename TaggedT > decltype (auto) visit ( VisitorT &&visitor, TaggedT &&arg ) { static_assert (IndexV < std::tuple_size_v>); using nth_t = std::tuple_element_t>; // If we're the last valid index then we must be invoked because // there's no other option. Do this, and (statically) avoid // further recursion. if constexpr (IndexV + 1 == std::tuple_size_v>) { return std::invoke (visitor, arg.template get ()); } else { // If the tag matches, then dispatch, else recurse with the // next available type index. if (arg.tag () == nth_t::tag) { return std::invoke (visitor, arg.template get ()); } else { return visit ( std::forward (visitor), arg ); } } } } template < typename VisitorT, template typename TaggedT, typename ...ComponentsT > requires std::is_same_v< std::remove_cvref_t>, tagged > decltype (auto) visit (VisitorT &&visitor, TaggedT &arg) { static_assert (sizeof...(ComponentsT)); return detail::visit<0> ( std::forward (visitor), arg ); } template < typename VisitorT, template typename TaggedT, typename ...ComponentsT > requires std::is_same_v< std::remove_cvref_t>, tagged > decltype (auto) visit (VisitorT &&visitor, TaggedT const &arg) { static_assert (sizeof...(ComponentsT)); return detail::visit<0> ( std::forward (visitor), arg ); } template bool operator== (tagged const &lhs, tagged const &rhs) { // Check the tags actually match to start with. This lets us use // short-circuiting in a second for the actual equality check. if (lhs.tag () != rhs.tag ()) return false; // Construct a fold-expression that tests every component type for // equality. Use short-circuiting with `is` to protect against `get` // queries for the wrong types. return ( ( lhs.template is () && (lhs.template get () == rhs.template get ()) ) || ... ); } template bool operator!= (tagged const &lhs, tagged const &rhs) { if (lhs.tag () != rhs.tag ()) return false; return ( ( lhs.template is () && (lhs.template get () != rhs.template get ()) ) || ... ); } }