diff --git a/CMakeLists.txt b/CMakeLists.txt index e43c2e2a..f21e5ecf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/test/types/tagged.cpp b/test/types/tagged.cpp new file mode 100644 index 00000000..7dab8173 --- /dev/null +++ b/test/types/tagged.cpp @@ -0,0 +1,42 @@ +#include + +#include + + +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 value (c {.value = 42 }); + + tap.expect_eq (value.tag (), c::tag, "initialisation sets the correct tag"); + tap.expect_eq (value.get ().value, 42u, "initialisation saves member variables"); + + tap.expect_nothrow ([&] () { value.get (); }, "getter after initialisation succeeds"); + + value = a { .value = 7 }; + tap.expect_eq (value.tag (), a::tag, "assignment changes the tag"); + tap.expect_eq (value.get ().value, 7, "assignment saves member variables"); + + return tap.status (); +} \ No newline at end of file diff --git a/types/tagged.hpp b/types/tagged.hpp new file mode 100644 index 00000000..ddc43041 --- /dev/null +++ b/types/tagged.hpp @@ -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 + */ + +#pragma once + +#include + +#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 + class tagged { + public: + using tuple_t = std::tuple; + using first_t = cruft::tuple::type::nth_t; + using tag_t = std::decay_t; + + static constexpr auto alignment = max (alignof (ValueT)...); + + /// 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_trivial_v && ...)); + + + //--------------------------------------------------------------------- + template + tagged (InitialT &&initial) + { + set (std::move (initial)); + } + + ~tagged () = default; + + tagged (tagged const&) = default; + tagged (tagged &&) = default; + + tagged& operator= (tagged const&) = default; + tagged& operator= (tagged &&) = default; + + + //--------------------------------------------------------------------- + template + NextT& + operator= (NextT &&next) + { + return set (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 + InnerT& + get (void)& + { + CHECK_EQ (InnerT::tag, m_tag); + return *cruft::cast::alignment (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 get () = 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)...) + ]; + }; +}