types: add 'tagged' union type
This commit is contained in:
parent
84ef2c1117
commit
6b00668692
@ -414,13 +414,14 @@ list (
|
||||
tuple/value.hpp
|
||||
typeidx.cpp
|
||||
typeidx.hpp
|
||||
types.hpp
|
||||
types/bits.hpp
|
||||
types/comparator.hpp
|
||||
types/description.cpp
|
||||
types/description.hpp
|
||||
types.hpp
|
||||
types/string.cpp
|
||||
types/string.hpp
|
||||
types/tagged.hpp
|
||||
types/traits.hpp
|
||||
uri.cpp
|
||||
uri.hpp
|
||||
@ -586,6 +587,7 @@ if (TESTS)
|
||||
tuple/value
|
||||
tuple/type
|
||||
typeidx
|
||||
types/tagged
|
||||
uri
|
||||
utf8
|
||||
vector
|
||||
|
42
test/types/tagged.cpp
Normal file
42
test/types/tagged.cpp
Normal file
@ -0,0 +1,42 @@
|
||||
#include <cruft/util/tap.hpp>
|
||||
|
||||
#include <cruft/util/types/tagged.hpp>
|
||||
|
||||
|
||||
enum tag_t { A, B, C };
|
||||
|
||||
std::ostream& operator<< (std::ostream& os, tag_t val)
|
||||
{
|
||||
switch (val) {
|
||||
case A: return os << "A";
|
||||
case B: return os << "B";
|
||||
case C: return os << "C";
|
||||
}
|
||||
|
||||
unreachable ();
|
||||
}
|
||||
|
||||
|
||||
struct a { static constexpr tag_t tag = A; int value; };
|
||||
struct b { static constexpr tag_t tag = B; bool value; };
|
||||
struct c { static constexpr tag_t tag = C; unsigned value; };
|
||||
|
||||
int main ()
|
||||
{
|
||||
cruft::TAP::logger tap;
|
||||
|
||||
// Don't initialise with something that is likely to have a zero tag in
|
||||
// case the memory has been pre-emptively or accidentally zeroed.
|
||||
cruft::tagged<a,b,c> value (c {.value = 42 });
|
||||
|
||||
tap.expect_eq (value.tag (), c::tag, "initialisation sets the correct tag");
|
||||
tap.expect_eq (value.get<c> ().value, 42u, "initialisation saves member variables");
|
||||
|
||||
tap.expect_nothrow ([&] () { value.get<c> (); }, "getter after initialisation succeeds");
|
||||
|
||||
value = a { .value = 7 };
|
||||
tap.expect_eq (value.tag (), a::tag, "assignment changes the tag");
|
||||
tap.expect_eq (value.get<a> ().value, 7, "assignment saves member variables");
|
||||
|
||||
return tap.status ();
|
||||
}
|
118
types/tagged.hpp
Normal file
118
types/tagged.hpp
Normal file
@ -0,0 +1,118 @@
|
||||
/*
|
||||
* 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 "../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)>;
|
||||
|
||||
static constexpr auto alignment = max (alignof (ValueT)...);
|
||||
|
||||
/// 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::move (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::move (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_EQ (InnerT::tag, m_tag);
|
||||
return *cruft::cast::alignment<InnerT*> (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 get<InnerT> () = std::move (inner);
|
||||
}
|
||||
|
||||
private:
|
||||
/// The tag value for the currently active type
|
||||
tag_t m_tag;
|
||||
|
||||
/// The storage area for all desired types.
|
||||
alignas (alignment) std::byte m_data[
|
||||
cruft::max (sizeof (ValueT)...)
|
||||
];
|
||||
};
|
||||
}
|
Loading…
Reference in New Issue
Block a user