libcruft-util/types/tagged.hpp
Danny Robson 3c78e19c99 types/tagged: prefer variadic alignas over explicit calculation
This reduces the scope for constant integral expression errors under GCC
9
2019-05-04 11:33:59 +10:00

127 lines
4.1 KiB
C++

/*
* 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>
#include "../cast.hpp"
#include "../maths.hpp"
#include "../tuple/type.hpp"
///////////////////////////////////////////////////////////////////////////////
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.
static_assert ((std::is_trivial_v<ValueT> && ...));
//---------------------------------------------------------------------
template <typename InitialT>
tagged (InitialT &&initial)
{
set<InitialT> (std::forward<InitialT> (initial));
}
~tagged () = default;
tagged (tagged const&) = default;
tagged (tagged &&) = default;
tagged& operator= (tagged const&) = default;
tagged& operator= (tagged &&) = default;
//---------------------------------------------------------------------
template <typename NextT>
NextT&
operator= (NextT &&next)
{
return set<NextT> (std::forward<NextT> (next));
}
///--------------------------------------------------------------------
/// Return the type code associated with the stored value.
auto tag (void) const { return m_tag; }
auto tag (void) { return 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 <typename InnerT>
InnerT&
get (void)&
{
CHECK (InnerT::tag == m_tag);
return *cruft::cast::alignment<std::decay_t<InnerT>*> (m_data + 0);
}
template <typename InnerT>
InnerT const&
get (void) const&
{
CHECK (InnerT::tag == m_tag);
return *cruft::cast::alignment<std::decay_t<InnerT> const*> (m_data + 0);
}
/// 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;
return *cruft::cast::alignment<std::decay_t<InnerT>*> (m_data + 0) = std::forward<InnerT> (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)...)
];
};
}