/* * 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 #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 (std::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. tag_t tag (void) const { return m_tag; } tag_t 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 { template requires ( std::is_invocable_v< VisitorT, decltype (std::declval ().template get ()) > ) decltype (auto) visit (VisitorT &&visitor, TaggedT &&arg) { if constexpr (sizeof ...(TailT) == 0) { return std::invoke (std::forward (visitor), arg.template get ()); } else { if (arg.tag () == HeadT::tag) { return std::invoke (std::forward (visitor), arg.template get ()); } else { return visit (std::forward (visitor), arg); } } } } /// Call the invokable `VisitorT` with the value in the supplied tagged /// union. 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&, ComponentsT...> ( std::forward (visitor), arg ); } /// Call the invokable `VisitorT` with the value in the supplied tagged /// union. 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 const&, ComponentsT...> ( 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 ()) ) || ... ); } }