2018-11-13 12:57:19 +11:00
|
|
|
/*
|
|
|
|
* 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 <danny@nerdcruft.net>
|
|
|
|
*/
|
|
|
|
|
|
|
|
#pragma once
|
|
|
|
|
|
|
|
#include <variant>
|
|
|
|
|
2018-11-13 15:20:10 +11:00
|
|
|
#include "../cast.hpp"
|
2018-11-13 12:57:19 +11:00
|
|
|
#include "../maths.hpp"
|
|
|
|
#include "../tuple/type.hpp"
|
|
|
|
|
2020-09-22 07:53:58 +10:00
|
|
|
#include <tuple>
|
|
|
|
|
2018-11-13 12:57:19 +11:00
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
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 <typename ...ValueT>
|
|
|
|
class tagged {
|
|
|
|
public:
|
|
|
|
using tuple_t = std::tuple<ValueT...>;
|
|
|
|
using first_t = cruft::tuple::type::nth_t<tuple_t,0>;
|
|
|
|
using tag_t = std::decay_t<decltype (first_t::tag)>;
|
|
|
|
|
|
|
|
/// All child types should have identical tag types
|
|
|
|
static_assert ((std::is_same_v<tag_t, std::decay_t<decltype(ValueT::tag)>> && ...));
|
|
|
|
|
|
|
|
/// All child types must be trivial, given we provide limited
|
|
|
|
/// construction and destruction support.
|
2019-07-15 14:34:55 +10:00
|
|
|
static_assert ((std::is_trivially_copyable_v<ValueT> && ...));
|
|
|
|
static_assert ((std::is_trivially_destructible_v<ValueT> && ...));
|
2018-11-13 12:57:19 +11:00
|
|
|
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------
|
|
|
|
template <typename InitialT>
|
2020-02-24 14:40:36 +11:00
|
|
|
requires (cruft::concepts::same_as<std::remove_cvref_t<InitialT>, ValueT> || ...)
|
2018-11-13 12:57:19 +11:00
|
|
|
tagged (InitialT &&initial)
|
|
|
|
{
|
2019-03-11 13:01:01 +11:00
|
|
|
set<InitialT> (std::forward<InitialT> (initial));
|
2018-11-13 12:57:19 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
~tagged () = default;
|
|
|
|
|
|
|
|
tagged (tagged const&) = default;
|
|
|
|
tagged (tagged &&) = default;
|
|
|
|
|
|
|
|
tagged& operator= (tagged const&) = default;
|
2020-09-22 07:53:58 +10:00
|
|
|
tagged& operator= (tagged &) = default;
|
2018-11-13 12:57:19 +11:00
|
|
|
tagged& operator= (tagged &&) = default;
|
|
|
|
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------
|
|
|
|
template <typename NextT>
|
|
|
|
NextT&
|
|
|
|
operator= (NextT &&next)
|
|
|
|
{
|
2019-03-11 13:01:01 +11:00
|
|
|
return set<NextT> (std::forward<NextT> (next));
|
2018-11-13 12:57:19 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
///--------------------------------------------------------------------
|
|
|
|
/// Return the type code associated with the stored value.
|
2020-12-02 13:04:45 +11:00
|
|
|
tag_t tag (void) const { return m_tag; }
|
|
|
|
tag_t tag (void) { return m_tag; }
|
2018-11-13 12:57:19 +11:00
|
|
|
|
|
|
|
|
2020-04-01 14:52:17 +11:00
|
|
|
/// Returns true if the contained type is the same as the type
|
|
|
|
/// specified in the template parameter.
|
|
|
|
template <typename QueryT>
|
|
|
|
bool is (void) const noexcept
|
|
|
|
{
|
|
|
|
return QueryT::tag == m_tag;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-11-13 12:57:19 +11:00
|
|
|
///--------------------------------------------------------------------
|
|
|
|
/// 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 <typename InnerT>
|
|
|
|
InnerT&
|
|
|
|
get (void)&
|
|
|
|
{
|
2019-03-11 12:11:03 +11:00
|
|
|
CHECK (InnerT::tag == m_tag);
|
2019-03-11 13:01:01 +11:00
|
|
|
return *cruft::cast::alignment<std::decay_t<InnerT>*> (m_data + 0);
|
2018-11-13 12:57:19 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-03-11 12:11:03 +11:00
|
|
|
template <typename InnerT>
|
|
|
|
InnerT const&
|
|
|
|
get (void) const&
|
|
|
|
{
|
|
|
|
CHECK (InnerT::tag == m_tag);
|
2019-03-11 13:01:01 +11:00
|
|
|
return *cruft::cast::alignment<std::decay_t<InnerT> const*> (m_data + 0);
|
2019-03-11 12:11:03 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-11-13 12:57:19 +11:00
|
|
|
/// Set the inner member to a supplied value and store the associated
|
|
|
|
/// type code.
|
|
|
|
template <typename InnerT>
|
|
|
|
InnerT&
|
|
|
|
set (InnerT &&inner)&
|
|
|
|
{
|
|
|
|
m_tag = inner.tag;
|
2019-03-11 13:01:01 +11:00
|
|
|
return *cruft::cast::alignment<std::decay_t<InnerT>*> (m_data + 0) = std::forward<InnerT> (inner);
|
2018-11-13 12:57:19 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
/// The tag value for the currently active type
|
|
|
|
tag_t m_tag;
|
|
|
|
|
|
|
|
/// The storage area for all desired types.
|
2019-05-04 11:33:59 +10:00
|
|
|
alignas (alignof (ValueT)...) std::byte m_data[
|
2018-11-13 12:57:19 +11:00
|
|
|
cruft::max (sizeof (ValueT)...)
|
|
|
|
];
|
|
|
|
};
|
2020-09-22 07:53:58 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
namespace std {
|
|
|
|
template <typename ...ComponentT>
|
|
|
|
struct tuple_size<::cruft::tagged<ComponentT...>>
|
|
|
|
: std::integral_constant<::std::size_t, sizeof...(ComponentT)>
|
|
|
|
{ ; };
|
|
|
|
|
|
|
|
|
|
|
|
template <std::size_t I, typename ...ComponentT>
|
|
|
|
struct tuple_element<
|
|
|
|
I,
|
|
|
|
::cruft::tagged<ComponentT...>
|
|
|
|
>
|
|
|
|
: ::std::tuple_element<
|
|
|
|
I,
|
|
|
|
std::tuple<ComponentT...>
|
|
|
|
>
|
|
|
|
{ ; };
|
|
|
|
}
|
2020-04-01 14:52:32 +11:00
|
|
|
|
|
|
|
|
2020-09-22 07:53:58 +10:00
|
|
|
namespace cruft {
|
2020-04-01 14:52:32 +11:00
|
|
|
namespace detail {
|
2020-12-02 13:05:26 +11:00
|
|
|
template <typename VisitorT, typename TaggedT, typename HeadT, typename ...TailT>
|
|
|
|
requires (
|
|
|
|
std::is_invocable_v<
|
|
|
|
VisitorT,
|
|
|
|
decltype (std::declval<TaggedT> ().template get <HeadT>())
|
|
|
|
>
|
|
|
|
)
|
2020-04-01 14:52:32 +11:00
|
|
|
decltype (auto)
|
2020-12-02 13:05:26 +11:00
|
|
|
visit (VisitorT &&visitor, TaggedT &&arg)
|
|
|
|
{
|
|
|
|
if constexpr (sizeof ...(TailT) == 0) {
|
|
|
|
return std::invoke (std::forward<VisitorT> (visitor), arg.template get<HeadT> ());
|
2020-04-01 14:52:32 +11:00
|
|
|
} else {
|
2020-12-02 13:05:26 +11:00
|
|
|
if (arg.tag () == HeadT::tag) {
|
|
|
|
return std::invoke (std::forward<VisitorT> (visitor), arg.template get<HeadT> ());
|
2020-04-01 14:52:32 +11:00
|
|
|
} else {
|
2020-12-02 13:05:26 +11:00
|
|
|
return visit<VisitorT, TaggedT, TailT...> (std::forward<VisitorT> (visitor), arg);
|
2020-04-01 14:52:32 +11:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-12-02 13:05:26 +11:00
|
|
|
/// Call the invokable `VisitorT` with the value in the supplied tagged
|
|
|
|
/// union.
|
2020-04-01 14:52:32 +11:00
|
|
|
template <
|
|
|
|
typename VisitorT,
|
|
|
|
template <typename...> typename TaggedT,
|
|
|
|
typename ...ComponentsT
|
|
|
|
>
|
|
|
|
requires std::is_same_v<
|
|
|
|
std::remove_cvref_t<TaggedT<ComponentsT...>>,
|
|
|
|
tagged<ComponentsT...>
|
|
|
|
>
|
|
|
|
decltype (auto)
|
2020-09-22 07:53:58 +10:00
|
|
|
visit (VisitorT &&visitor, TaggedT<ComponentsT...> &arg)
|
|
|
|
{
|
|
|
|
static_assert (sizeof...(ComponentsT));
|
|
|
|
|
2020-12-02 13:05:26 +11:00
|
|
|
return detail::visit<VisitorT, TaggedT<ComponentsT...>&, ComponentsT...> (
|
2020-09-22 07:53:58 +10:00
|
|
|
std::forward<VisitorT> (visitor),
|
|
|
|
arg
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-12-02 13:05:26 +11:00
|
|
|
/// Call the invokable `VisitorT` with the value in the supplied tagged
|
|
|
|
/// union.
|
2020-09-22 07:53:58 +10:00
|
|
|
template <
|
|
|
|
typename VisitorT,
|
|
|
|
template <typename...> typename TaggedT,
|
|
|
|
typename ...ComponentsT
|
|
|
|
>
|
|
|
|
requires std::is_same_v<
|
|
|
|
std::remove_cvref_t<TaggedT<ComponentsT...>>,
|
|
|
|
tagged<ComponentsT...>
|
|
|
|
>
|
|
|
|
decltype (auto)
|
|
|
|
visit (VisitorT &&visitor, TaggedT<ComponentsT...> const &arg)
|
2020-04-01 14:52:32 +11:00
|
|
|
{
|
|
|
|
static_assert (sizeof...(ComponentsT));
|
|
|
|
|
2020-12-02 13:05:26 +11:00
|
|
|
return detail::visit<VisitorT, TaggedT<ComponentsT...> const&, ComponentsT...> (
|
2020-04-01 14:52:32 +11:00
|
|
|
std::forward<VisitorT> (visitor),
|
|
|
|
arg
|
|
|
|
);
|
|
|
|
}
|
2020-07-21 15:05:21 +10:00
|
|
|
|
|
|
|
|
|
|
|
template <typename ...ComponentsT>
|
|
|
|
bool operator== (tagged<ComponentsT...> const &lhs, tagged<ComponentsT...> 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<ComponentsT> () &&
|
|
|
|
(lhs.template get<ComponentsT> () == rhs.template get<ComponentsT> ())
|
|
|
|
) || ...
|
|
|
|
);
|
|
|
|
}
|
2020-10-21 11:22:51 +11:00
|
|
|
|
2020-12-02 13:05:26 +11:00
|
|
|
|
2020-10-21 11:22:51 +11:00
|
|
|
template <typename ...ComponentsT>
|
|
|
|
bool operator!= (tagged<ComponentsT...> const &lhs, tagged<ComponentsT...> const &rhs)
|
|
|
|
{
|
|
|
|
if (lhs.tag () != rhs.tag ())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return (
|
|
|
|
(
|
|
|
|
lhs.template is<ComponentsT> () &&
|
|
|
|
(lhs.template get<ComponentsT> () != rhs.template get<ComponentsT> ())
|
|
|
|
) || ...
|
|
|
|
);
|
|
|
|
}
|
2018-11-13 12:57:19 +11:00
|
|
|
}
|