diff --git a/Control/CxxUtils/CMakeLists.txt b/Control/CxxUtils/CMakeLists.txt index 0e1513372308bbd9fa90514ef67f2abcd1408457..bf26f6102a71d2ba3e049c38e72495dcd07bc480 100644 --- a/Control/CxxUtils/CMakeLists.txt +++ b/Control/CxxUtils/CMakeLists.txt @@ -102,6 +102,12 @@ atlas_add_test( exctrace2_test LINK_LIBRARIES CxxUtils LOG_IGNORE_PATTERN "no version information available" ) +atlas_add_test( + OptionalContainer_test + SOURCES test/OptionalContainer_test.cxx + LINK_LIBRARIES CxxUtils +) + # Set up the "simple" tests: foreach( test sincos_test ArrayScanner_test Arrayrep_test Array_test PackedArray_test pointer_list_test FloatPacker_test diff --git a/Control/CxxUtils/CxxUtils/OptionalContainer.h b/Control/CxxUtils/CxxUtils/OptionalContainer.h new file mode 100644 index 0000000000000000000000000000000000000000..095796ebdaeeeebea2cccd87852db164f69f77df --- /dev/null +++ b/Control/CxxUtils/CxxUtils/OptionalContainer.h @@ -0,0 +1,404 @@ +/* + * Copyright (C) 2002-2020 CERN for the benefit of the ATLAS collaboration + + * @file CxxUtils/OptionalContainer.h + * @author Stephen Nicholas Swatman <stephen.nicholas.swatman@cern.ch> + * @date September 2020 + * @brief A container of optional values, with ergonomic methods. + */ + +#ifndef CXXUTILS_OPTIONALCONTAINER_H +#define CXXUTILS_OPTIONALCONTAINER_H + +#include <optional> +#include <array> +#include <vector> + +namespace { + /** + * @brief Helper struct to allow us to partially apply the size parameter + * to std::array. + * + * This class allows us to partially apply the std::array templated class + * with only the size argument, allowing us to apply the contained type + * typename later, completing the type. + * + * If this seems like an odd idea, it may be useful to approach the problem + * from a type theoretical view, and abstract away from the cold hard C++ + * for just a moment. Specifically, we need to realize that std::array + * (without the template parameters applied) is essentially a binary + * function _at the type level_ (called a type constructor); it takes an + * argument of kind (which is what we call "types" in the typed lambda + * calculus one level above the base) typename, and a size_t, and returns + * a concrete type. In C++ like syntax, it's a function at the type level + * with the following signature: + * + * typename std::array(typename T, size_t N); + * + * If you're more familiar with a functional syntax like Haskell's, then + * firstly I weep for you because trying to do any type-level computation + * in C++ is a pain, but you may also find the following syntax more easy + * to read (assuming DataKinds): + * + * std::array :: * -> Integer -> * + * + * Standard C++ syntax allows us to only fully apply these types, we can't + * apply them partially (just like how we cannot partially apply normal + * functions). In other words, we cannot pass std::array a contained type + * only, capturing that typename in a way reminiscent of a closure, giving + * us a partially applied type constructor with signature: + * + * template <typename> typename std::array'(size_t N); + * std::array :: Integer -> * + * + * And we certainly cannot partially apply in the reverse order, passing it + * a size_t for the array size and then passing it the container type to + * get a partially applied type constructor: + * + * template <size_t> typename std::array''(typename T); + * std::array :: * -> * + * + * However, this is something we would very much like to do. Indeed, we + * want our optional container to be relatively type-agnostic, and for that + * to be possible the type of our OptionalBase has to be (where C is our + * underlying container type): + * + * typname OptionalBase(typename (*C)(typename), ...); + * OptionalBase :: (* -> *) -> ... -> * + * + * So, for example, we could pass the std::vector type constructor as a + * template parameter of OptionalBase directly, because it has a single + * argument of kind typename. However, std::array does not do this by + * default, since it has two arguments. We need to partially apply the size + * argument before we can pass it to OptionalBase in a way that is similar + * to the way we would pass std::vector. + * + * Wrapping std::array in a templated class with a templated alias allows + * us this. Indeed, PartiallyAppliedArray has the type: + * + * typename PartiallyAppliedArray(size_t); + * + * So, for example, we can construct the type PartiallyAppliedArray<10> to + * get a struct wrapping std::array with the size parameter partially + * applied with the value 10. This type then contains a type alias, type, + * or PartiallyAppliedArray<10>::type which is a unary type constructor + * with a single argument, which is exactly what we want. So, we can now + * pass this type directly to OptionalBase, or we can construct an array of + * 10 integers from it by writing PartiallyAppliedArray<10>::type<int>. + * + * @tparam N the size of the array. + */ + template<std::size_t N> + struct PartiallyAppliedArray { + template<typename T> + using type = std::array<T, N>; + }; + + /** + * @brief Helper struct to give compile-time info about static castability. + * + * In our following definitions, we want to know whether a type A is + * castable to a type B. For this, we need to use some helper structs which + * we can use in static assertsions. This is relatively standard procedure + * for C++ template metaprogramming, and recipes and explanations of this + * kind of code can be found all across the internet. + * + * This first type is the default case, were F turns out to not be + * statically castable to T. It subclasses the false type, so ::value will + * return false. + */ + template <class F, class T, class=T> + struct is_static_castable : std::false_type + {}; + + /** + * This is the specialized sibling of the type above, in which F can indeed + * be converted to T. It subclasses the true type, so ::value will return + * true. + */ + template <class F, class T> + struct is_static_castable<F, T, decltype(static_cast<T>(std::declval<F>()))> : std::true_type + {}; +} + +namespace CxxUtils { + /** + * @brief The base class representing a container filled with optional + * values. + * + * This is the meat of the pudding of our optional container types. It + * allows us to check whether values are set, set them, unset them, and a + * few more things. + * + * @tparam C the underlying container type, must take a single typename + * argument representing the contained type. + * @tparam I the index type, which will usually be size_t but can also be + * any value which is convertible to size_t, or other types if the + * container is map-like. + * @tparam V the contained type, without the optional type constructor + * applied to it. + * + * As an example, OptionalBase<std::vector, MyEnum, std::string> is an + * optional container backed by a vector, indexed by an enumeration type, + * containing optional strings. + * + * Of course, I could be derived easily from C<std::optional<V>>::size_type + * but the approach of having a separately and explicitly defined template + * variable for this is more powerful, since it also allows us to index + * with, for example, enumeration types in a type-safe fashion. + */ + template<template <typename> typename C, typename I, typename V> + class OptionalBase { + public: + /** + * F is the inner type, which is the type constructor of the underlying + * container type C applied to the std::optional type constructor, + * applied in turn to the value type V. + */ + using F = C<std::optional<V>>; + + static_assert( + is_static_castable<I, typename F::size_type>::value, + "Index type must be castable to storage class index type!" + ); + + /** + * @brief Check whether the specified index is set. + * + * This method is used to determine whether a value in the array is set + * or unset. + * + * @param i the index to check. + * @return true if the value at i is set, false otherwise. + */ + bool is_set(const I & i) const; + + /** + * @brief Update an index that is already set. + * + * This method updates the value at a given index, but only if that + * value is already set. If it is unset, this method is a noop. + * + * @param i the index to update. + * @param v the value to be set. + * @tparam W the value type, should be convertible to V. + * @return true if the update succeeds, otherwise false. + * + * @note The fact that this method is templated with a seemingly + * useless template variable W might be confusing, but it has to do + * with a concept that is colloquially known as universal references, + * where a single function can accept both lvalue references and rvalue + * references in an efficient way. This relies on templating, and + * requires a separate function level template separate from the class + * level template variable V. + */ + template<typename W> + bool update(const I & i, W && v); + + /** + * @brief Set an index that is not already set. + * + * This method sets the value at a given index, but only if that value + * is not already set. If the value is set, this method is a noop. + * + * @param i the index to set. + * @param v the value to be set. + * @tparam W the value type, should be convertible to V. + * @return true if the set succeeds, otherwise false. + * + * @note See the comment on update() for more info about W. + */ + template<typename W> + bool set_if_unset(const I & i, W && v); + + /** + * @brief Update the value of a key, regardless of existing value. + * + * This method inserts a new value at a given index, regardless of + * whether a value already exists at that location. This operation + * always succeeds. + * + * @param i the index to set. + * @param v the value to be set. + * @tparam W the value type, should be convertible to V. + * @return true, always. + * + * @note The return value of this method is superfluous, but it's there + * to preserve the symmetry with the update and set methods. + * @note See the comment on update() for more info about W. + */ + template<typename W> + bool set(const I & i, W && v); + + /** + * @brief Unset the value at a given key. + * + * This method removes the value at a given index, and sets the value + * to undefined. This also deletes the old value. This operation always + * succeeds. + * + * @param i the index to unset. + */ + void unset(const I & i); + + /** + * @brief Increment the value at a given index by one. + * + * This method increases the value of a given key by one. For this + * method to exist, the underlying type T must be incrementable. As of + * C++20, this can be represented using the std::incrementable + * constraint, but not yet. + * + * @param i the index to increment. + */ + void increment(const I & i); + + /** + * @brief Get the value at a given key (const). + * + * Getter for the value at a given location. Returns an optional + * reference, since we cannot guarantee that the object exists. + * + * @param i the index to retrieve. + * @return a reference to the optional value held at the index. + */ + const std::optional<V> & get(const I & i) const; + + /** + * @brief Get the value at a given key. + * + * Same as get(), but not const. + * + * @param i the index to retrieve. + * @return a reference to the optional value held at the index. + */ + std::optional<V> & get(const I & i); + + /** + * @brief Coalesce a single value in the array, giving priority to the + * left hand side. + * + * Updates a field in the array, giving priority to the left hand side. + * The workings of this methods are concisely described by the + * following case analysis: + * + * v << w = case (v, w) of: + * Just u, _ = Just u + * _, u = u + * + * The argument value takes std::optional parameters, but the method + * may also be passed V directly which will then be boxed implicitly. + * + * @param i the index to coalesce at. + * @param v the value to coalesce with. + */ + void coalesce_l(const I & i, const std::optional<V> & v); + + /** + * @brief Coalesce a single value in the array, giving priority to the + * right hand side. + * + * Same as coalesce_l(), except the case analysis is as follows: + * + * v >> w = case (v, w) of: + * _, Just u = Just u + * u, _ = u + * + * @param i the index to coalesce at. + * @param v the value to coalesce with. + */ + void coalesce_r(const I & i, const std::optional<V> & v); + + /** + * @brief Mutable accessor to the underlying container. + * + * A simple mutable accessor to the underlying container of this class. + * + * @return a mutable reference to the underlying container. + */ + F & data(void); + + /** + * @brief Immutable accessor to the underlying container. + * + * A simple immutable accessor to the underlying container of this + * class. + * + * @return an immutable reference to the underlying container. + */ + const F & data(void) const; + + protected: + F m_data; + }; + + /** + * @brief Specialization of OptionalBase indexed by discrete, countable + * indices. + * + * If we know that our indices are contiguous and discrete, this allows us + * additional power, such as allowing us to coalesce over the entire set of + * values. + * + * Template parameters are the same as for OptionalBase. + */ + template<template <typename> typename C, typename I, typename V> + class OptionalArrayLike : public OptionalBase<C, I, V> { + static_assert( + is_static_castable<I, std::size_t>::value, + "Index type must be castable to std::size_t!" + ); + + public: + /** + * @brief Coalesce this array with another, giving priority to the left + * hand side. + * + * Updates the values in the array according to the data in the right + * hand side array, giving priority to the left hand side. This method + * changes the values of the array on which it is called. + * + * For example, if we abuse notation and write left-coalesce as <<, we + * find the following: + * + * [5, {}, 7, {}] << [2, 9, {}, {}] = [5, 9, 7, {}] + * + * @param rhs the right hand side array. + */ + void coalesce_all_l(const OptionalArrayLike<C, I, V> & rhs); + + /** + * @brief Coalesce this array with another, giving priority to the + * right hand side. + * + * See coalesce_l() for more information. As an example, if we abuse + * notation and write right-coalesce as >>, we find the following: + * + * [5, {}, 7, {}] >> [2, 9, {}, {}] = [2, 9, 7, {}] + * + * @param rhs the right hand side track summary. + */ + void coalesce_all_r(const OptionalArrayLike<C, I, V> & rhs); + }; + + /** + * @brief An alias to a specialization of an optional array. + * + * This is the only pecialization we provide so far, and it's for the array + * backend. The template arguments are compatible with those of std::array, + * with the exception that it can optionally take an indexing type as well. + * + * @tparam V the underlying value type. + * @tparam N the size of the array. + * @tparam I the indexing type, size_t by default. + * + * This is where our PartiallyAppliedArray class comes into play, as it + * allows us to create a partially applied array type of given size. + */ + template<typename V, std::size_t N, typename I=std::size_t> + using OptionalArray = OptionalArrayLike<PartiallyAppliedArray<N>::template type, I, V>; +} + +#include "CxxUtils/OptionalContainer.icc" + +#endif diff --git a/Control/CxxUtils/CxxUtils/OptionalContainer.icc b/Control/CxxUtils/CxxUtils/OptionalContainer.icc new file mode 100644 index 0000000000000000000000000000000000000000..3a3479e0eecf3071aa11b85a2c330dd6cd8e07e3 --- /dev/null +++ b/Control/CxxUtils/CxxUtils/OptionalContainer.icc @@ -0,0 +1,107 @@ +/* +* Copyright (C) 2002-2020 CERN for the benefit of the ATLAS collaboration + +* @file CxxUtils/OptionalContainer.icc +* @author Stephen Nicholas Swatman <stephen.nicholas.swatman@cern.ch> +* @date September 2020 +* @brief A container of optional values, with ergonomic methods. +*/ + +template<template <typename> typename C, typename I, typename V> +bool CxxUtils::OptionalBase<C, I, V>::is_set(const I & i) const { + return this->get(i).has_value(); +} + +template<template <typename> typename C, typename I, typename V> +template<typename W> +bool CxxUtils::OptionalBase<C, I, V>::update(const I & i, W && v) { + static_assert(std::is_convertible<V, std::remove_reference_t<W>>::value); + + if (this->get(i).has_value()) { + this->get(i) = std::forward<W>(v); + return true; + } else { + return false; + } +} + +template<template <typename> typename C, typename I, typename V> +template<typename W> +bool CxxUtils::OptionalBase<C, I, V>::set_if_unset(const I & i, W && v) { + static_assert(std::is_convertible<V, std::remove_reference_t<W>>::value); + + if (this->get(i).has_value()) { + return false; + } else { + this->get(i) = std::forward<W>(v); + return true; + } +} + +template<template <typename> typename C, typename I, typename V> +template<typename W> +bool CxxUtils::OptionalBase<C, I, V>::set(const I & i, W && v) { + static_assert(std::is_convertible<V, std::remove_reference_t<W>>::value); + + this->get(i) = std::forward<W>(v); + return true; +} + +template<template <typename> typename C, typename I, typename V> +void CxxUtils::OptionalBase<C, I, V>::unset(const I & i) { + this->get(i).reset(); +} + +template<template <typename> typename C, typename I, typename V> +void CxxUtils::OptionalBase<C, I, V>::increment(const I & i) { + this->set_if_unset(i, static_cast<V>(0)); + (*this->get(i))++; +} + +template<template <typename> typename C, typename I, typename V> +const std::optional<V> & CxxUtils::OptionalBase<C, I, V>::get(const I & i) const { + return this->m_data[static_cast<typename F::size_type>(i)]; +} + +template<template <typename> typename C, typename I, typename V> +std::optional<V> & CxxUtils::OptionalBase<C, I, V>::get(const I & i) { + return this->m_data[static_cast<typename F::size_type>(i)]; +} + +template<template <typename> typename C, typename I, typename V> +void CxxUtils::OptionalBase<C, I, V>::coalesce_l(const I & i, const std::optional<V> & v) { + if (v.has_value()) { + this->set_if_unset(i, *v); + } +} + +template<template <typename> typename C, typename I, typename V> +void CxxUtils::OptionalBase<C, I, V>::coalesce_r(const I & i, const std::optional<V> & v) { + if (v.has_value()) { + this->set(i, *v); + } +} + +template<template <typename> typename C, typename I, typename V> +auto CxxUtils::OptionalBase<C, I, V>::data(void) -> F & { + return m_data; +} + +template<template <typename> typename C, typename I, typename V> +auto CxxUtils::OptionalBase<C, I, V>::data(void) const -> const F & { + return m_data; +} + +template<template <typename> typename C, typename I, typename V> +void CxxUtils::OptionalArrayLike<C, I, V>::coalesce_all_l(const OptionalArrayLike<C, I, V> & rhs) { + for (size_t i = 0; i < std::min(this->m_data.size(), rhs.m_data.size()); i++) { + this->coalesce_l(i, rhs.get(i)); + } +} + +template<template <typename> typename C, typename I, typename V> +void CxxUtils::OptionalArrayLike<C, I, V>::coalesce_all_r(const OptionalArrayLike<C, I, V> & rhs) { + for (size_t i = 0; i < std::min(this->m_data.size(), rhs.m_data.size()); i++) { + this->coalesce_r(i, rhs.get(i)); + } +} diff --git a/Control/CxxUtils/test/OptionalContainer_test.cxx b/Control/CxxUtils/test/OptionalContainer_test.cxx new file mode 100644 index 0000000000000000000000000000000000000000..208f7a11d49357d4df2d95851e525e3753656d39 --- /dev/null +++ b/Control/CxxUtils/test/OptionalContainer_test.cxx @@ -0,0 +1,398 @@ +#undef NDEBUG + +#include <cassert> +#include <cstddef> +#include <memory> + +#include "CxxUtils/OptionalContainer.h" + + +class Test { +public: + Test(int v) : m_t(v) { + + }; + + int get() { + return m_t; + }; +private: + int m_t; +}; + + +enum class TestEnum { + Nil = 0, + One = 1, + Two = 2, + Three = 3, + Max = 4 +}; + + +void test_init(void) { + CxxUtils::OptionalArray<int, 2> A1; + + assert(!A1.get(0).has_value()); + assert(!A1.get(1).has_value()); +} + +void test_set(void) { + CxxUtils::OptionalArray<int, 3> A1; + + assert(A1.set_if_unset(0, 5)); + assert(!A1.set_if_unset(0, 2)); + assert(A1.set_if_unset(1, 6)); + + assert(A1.is_set(0)); + assert(A1.is_set(1)); + assert(!A1.is_set(2)); + + assert(A1.get(0) == 5); + assert(A1.get(1) == 6); +} + +void test_copy_ctor(void) { + CxxUtils::OptionalArray<int, 3> A1; + + A1.set_if_unset(0, 5); + A1.set_if_unset(1, 6); + + CxxUtils::OptionalArray<int, 3> A2(A1); + + assert(A1.is_set(0)); + assert(A1.is_set(1)); + assert(!A1.is_set(2)); + + assert(A1.get(0) == 5); + assert(A1.get(1) == 6); + + assert(A2.is_set(0)); + assert(A2.is_set(1)); + assert(!A2.is_set(2)); + + assert(A2.get(0) == 5); + assert(A2.get(1) == 6); +} + +void test_copy_op(void) { + CxxUtils::OptionalArray<int, 3> A1; + + A1.set_if_unset(0, 5); + A1.set_if_unset(1, 6); + + CxxUtils::OptionalArray<int, 3> A2 = A1; + + assert(A1.is_set(0)); + assert(A1.is_set(1)); + assert(!A1.is_set(2)); + + assert(A1.get(0) == 5); + assert(A1.get(1) == 6); + + assert(A2.is_set(0)); + assert(A2.is_set(1)); + assert(!A2.is_set(2)); + + assert(A2.get(0) == 5); + assert(A2.get(1) == 6); +} + +void test_update(void) { + CxxUtils::OptionalArray<int, 3> A1; + + assert(A1.set_if_unset(0, 5)); + assert(A1.set_if_unset(1, 6)); + + assert(A1.update(0, 2)); + assert(A1.update(1, 7)); + assert(!A1.update(2, 5)); + + assert(A1.is_set(0)); + assert(A1.is_set(1)); + assert(!A1.is_set(2)); + + assert(A1.get(0) == 2); + assert(A1.get(1) == 7); +} + +void test_upset(void) { + CxxUtils::OptionalArray<int, 3> A1; + + assert(A1.set_if_unset(0, 5)); + assert(A1.set_if_unset(1, 6)); + + assert(A1.set(0, 2)); + assert(A1.set(1, 7)); + assert(A1.set(2, 5)); + + assert(A1.is_set(0)); + assert(A1.is_set(1)); + assert(A1.is_set(2)); + + assert(A1.get(0) == 2); + assert(A1.get(1) == 7); + assert(A1.get(2) == 5); +} + +void test_unset(void) { + CxxUtils::OptionalArray<int, 3> A1; + + assert(A1.set_if_unset(0, 5)); + assert(A1.set_if_unset(1, 6)); + + assert(A1.is_set(0)); + assert(A1.is_set(1)); + assert(!A1.is_set(2)); + + A1.unset(1); + + assert(A1.is_set(0)); + assert(!A1.is_set(1)); + assert(!A1.is_set(2)); +} + +void test_increment(void) { + CxxUtils::OptionalArray<int, 3> A1; + + assert(A1.set_if_unset(0, 5)); + + assert(A1.is_set(0)); + assert(!A1.is_set(1)); + assert(!A1.is_set(2)); + + A1.increment(0); + A1.increment(1); + + assert(A1.is_set(0)); + assert(A1.is_set(1)); + assert(!A1.is_set(2)); + + assert(A1.get(0) == 6); + assert(A1.get(1) == 1); +} + +void test_coalese_l_whole(void) { + CxxUtils::OptionalArray<int, 4> A1; + CxxUtils::OptionalArray<int, 4> A2; + + A1.set_if_unset(0, 5); + A1.set_if_unset(1, 7); + + A2.set_if_unset(0, 11); + A2.set_if_unset(2, 9); + + A1.coalesce_all_l(A2); + + assert(A1.is_set(0)); + assert(A1.is_set(1)); + assert(A1.is_set(2)); + assert(!A1.is_set(3)); + + assert(A1.get(0) == 5); + assert(A1.get(1) == 7); + assert(A1.get(2) == 9); +} + +void test_coalese_r_whole(void) { + CxxUtils::OptionalArray<int, 4> A1; + CxxUtils::OptionalArray<int, 4> A2; + + A1.set_if_unset(0, 5); + A1.set_if_unset(1, 7); + + A2.set_if_unset(0, 11); + A2.set_if_unset(2, 9); + + A1.coalesce_all_r(A2); + + assert(A1.is_set(0)); + assert(A1.is_set(1)); + assert(A1.is_set(2)); + assert(!A1.is_set(3)); + + assert(A1.get(0) == 11); + assert(A1.get(1) == 7); + assert(A1.get(2) == 9); +} + +void test_coalese_l_value(void) { + CxxUtils::OptionalArray<int, 4> A1; + std::optional<int> V1 = 3; + std::optional<int> V2 = {}; + std::optional<int> V3 = 9; + std::optional<int> V4 = {}; + + A1.set_if_unset(0, 5); + A1.set_if_unset(1, 7); + + A1.coalesce_l(0, V1); + A1.coalesce_l(1, V2); + A1.coalesce_l(2, V3); + A1.coalesce_l(3, V4); + + assert(A1.is_set(0)); + assert(A1.is_set(1)); + assert(A1.is_set(2)); + assert(!A1.is_set(3)); + + assert(A1.get(0) == 5); + assert(A1.get(1) == 7); + assert(A1.get(2) == 9); +} + +void test_coalese_r_value(void) { + CxxUtils::OptionalArray<int, 4> A1; + std::optional<int> V1 = 3; + std::optional<int> V2 = {}; + std::optional<int> V3 = 9; + std::optional<int> V4 = {}; + + A1.set_if_unset(0, 5); + A1.set_if_unset(1, 7); + + A1.coalesce_r(0, V1); + A1.coalesce_r(1, V2); + A1.coalesce_r(2, V3); + A1.coalesce_r(3, V4); + + assert(A1.is_set(0)); + assert(A1.is_set(1)); + assert(A1.is_set(2)); + assert(!A1.is_set(3)); + + assert(A1.get(0) == 3); + assert(A1.get(1) == 7); + assert(A1.get(2) == 9); +} + +void test_coalese_upcast_value(void) { + CxxUtils::OptionalArray<int, 8> A1; + + A1.coalesce_l(0, 5); + assert(A1.is_set(0)); + assert(A1.get(0) == 5); + A1.coalesce_r(0, 3); + assert(A1.is_set(0)); + assert(A1.get(0) == 3); + + A1.coalesce_l(1, {}); + assert(!A1.is_set(1)); + A1.coalesce_r(1, 5); + assert(A1.is_set(1)); + assert(A1.get(1) == 5); + + A1.coalesce_l(2, 2); + assert(A1.is_set(2)); + assert(A1.get(2) == 2); + A1.coalesce_r(2, {}); + assert(A1.is_set(2)); + assert(A1.get(2) == 2); + + A1.coalesce_l(3, {}); + assert(!A1.is_set(3)); + A1.coalesce_r(3, {}); + assert(!A1.is_set(3)); +} + +void test_copyable_type(void) { + CxxUtils::OptionalArray<Test, 8> A1; + + Test t1(5); + Test t2(7); + const Test t3(9); + const Test t4(11); + + A1.set_if_unset(0, Test(1)); + A1.set_if_unset(1, t1); + A1.set_if_unset(2, std::move(t2)); + A1.set_if_unset(3, t3); + A1.set_if_unset(4, std::move(t4)); + + assert(A1.is_set(0)); + assert(A1.is_set(1)); + assert(A1.is_set(2)); + assert(A1.is_set(3)); + assert(A1.is_set(4)); + + assert(A1.get(0)->get() == 1); + assert(A1.get(1)->get() == 5); + assert(A1.get(2)->get() == 7); + assert(A1.get(3)->get() == 9); + assert(A1.get(4)->get() == 11); +} + +void test_non_copyable_type(void) { + CxxUtils::OptionalArray<std::unique_ptr<int>, 8> A1; + + std::unique_ptr<int> t1(new int(5)); + std::unique_ptr<int> t2 = std::make_unique<int>(7); + + A1.set_if_unset(0, std::make_unique<int>(1)); + A1.set_if_unset(1, std::move(t1)); + A1.set_if_unset(2, std::move(t2)); + + assert(A1.is_set(0)); + assert(A1.is_set(1)); + assert(A1.is_set(2)); + + assert(**A1.get(0) == 1); + assert(**A1.get(1) == 5); + assert(**A1.get(2) == 7); +} + +void test_class_enum_index(void) { + CxxUtils::OptionalArray<int, static_cast<size_t>(TestEnum::Max), TestEnum> A1; + + A1.set_if_unset(TestEnum::Nil, 2); + A1.set_if_unset(TestEnum::One, 3); + A1.set_if_unset(TestEnum::Two, 4); + A1.set_if_unset(TestEnum::Three, 5); + + assert(A1.is_set(TestEnum::Nil)); + assert(A1.is_set(TestEnum::One)); + assert(A1.is_set(TestEnum::Two)); + assert(A1.is_set(TestEnum::Three)); + + assert(*A1.get(TestEnum::Nil) == 2); + assert(*A1.get(TestEnum::One) == 3); + assert(*A1.get(TestEnum::Two) == 4); + assert(*A1.get(TestEnum::Three) == 5); +} + +void test_implicit_convert() { + CxxUtils::OptionalArray<float, 2> A1; + + assert(!A1.is_set(0)); + assert(!A1.is_set(1)); + + A1.set_if_unset(0, 5); + assert(A1.is_set(0)); + A1.update(0, 6); + assert(A1.is_set(0)); + A1.set(0, 8); + assert(A1.is_set(0)); + + assert(*A1.get(0) == 8.0); +} + +int main() { + test_init(); + test_set(); + test_copy_ctor(); + test_copy_op(); + test_update(); + test_upset(); + test_unset(); + test_increment(); + test_coalese_l_whole(); + test_coalese_r_whole(); + test_coalese_l_value(); + test_coalese_r_value(); + test_coalese_upcast_value(); + test_copyable_type(); + test_non_copyable_type(); + test_class_enum_index(); + test_implicit_convert(); + return 0; +}