diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..137266e --- /dev/null +++ b/.editorconfig @@ -0,0 +1,4 @@ +root = true + +[*.{cpp,h,hpp,ixx,ipp}] +charset = utf-8 diff --git a/include/iris/container_traits.hpp b/include/iris/container_traits.hpp new file mode 100644 index 0000000..eaa095f --- /dev/null +++ b/include/iris/container_traits.hpp @@ -0,0 +1,671 @@ +#ifndef IRIS_ZZ_CONTAINER_TRAITS_HPP +#define IRIS_ZZ_CONTAINER_TRAITS_HPP + +// SPDX-License-Identifier: MIT + +#include // IWYU pragma: keep + +#include // IWYU pragma: keep +#include // IWYU pragma: keep + +#include +#include +#include +#include +#include + +namespace iris::container { + +template +concept mapping_container = + ranges::mapping_range && + std::default_initializable> && + requires( + std::remove_cvref_t& c, + typename std::remove_cvref_t::key_type k, + typename std::remove_cvref_t::mapped_type v + ) { + c.emplace(std::move(k), std::move(v)); + }; + +template +concept unique_mapping_container = + mapping_container && + requires( + std::remove_cvref_t& c, + typename std::remove_cvref_t::key_type k, + typename std::remove_cvref_t::mapped_type v + ) { + { c.try_emplace(std::move(k), std::move(v)).second } -> std::convertible_to; + { c.insert_or_assign(std::move(k), std::move(v)).second } -> std::convertible_to; + }; + + +namespace detail { + +template +concept has_front = requires(ContainerT& cont) { + { cont.front() } -> std::same_as>; +}; + +template +concept has_back = requires(ContainerT& cont) { + { cont.back() } -> std::same_as>; +}; + +struct front_fn +{ + template + requires has_front + [[nodiscard]] static constexpr decltype(auto) operator()(R&& r) + noexcept(noexcept(r.front())) + { + return r.front(); + } + + template + requires (!has_front) && std::ranges::input_range + [[nodiscard]] static constexpr decltype(auto) operator()(R&& r) + noexcept(noexcept(*std::ranges::begin(r))) + { + return *std::ranges::begin(r); + } +}; + +struct back_fn +{ + template + requires has_back + [[nodiscard]] static constexpr decltype(auto) operator()(R&& r) + noexcept(noexcept(r.back())) + { + return r.back(); + } + + template + requires + (!has_back) && + std::ranges::bidirectional_range && + std::ranges::common_range + [[nodiscard]] static constexpr decltype(auto) operator()(R&& r) + noexcept(noexcept(*std::ranges::prev(std::ranges::end(r)))) + { + return *std::ranges::prev(std::ranges::end(r)); + } +}; + +} // detail + +[[maybe_unused]] inline constexpr detail::front_fn front{}; +[[maybe_unused]] inline constexpr detail::back_fn back{}; + +template concept front_accessible = requires(R& r) { front(r); }; +template concept back_accessible = requires(R& r) { back(r); }; + + +// ------------------------------------------------------------ + +namespace detail { + +template +concept has_emplace_front = + requires(ContainerT& cont) { + cont.emplace_front(std::declval()...); + }; + +template +concept has_push_front = + sizeof...(Args) == 1 && + requires(ContainerT& cont) { + cont.push_front(std::declval()...); + }; + +template +concept has_begin_emplace = + std::ranges::range && + requires(ContainerT& cont) { + cont.emplace(std::ranges::begin(cont), std::declval()...); + }; + +template +concept has_begin_insert = + sizeof...(Args) == 1 && + std::ranges::range && + requires(ContainerT& cont) { + cont.insert(std::ranges::begin(cont), std::declval()...); + }; + +} // detail + +template +concept front_pushable = + detail::has_emplace_front || + detail::has_push_front; + +template +concept default_prependable = + std::ranges::range && + std::default_initializable> && + ( + detail::has_emplace_front || detail::has_begin_emplace || + ( + std::move_constructible> && + ( + detail::has_push_front> || + detail::has_begin_insert> + ) + ) + ); + +template +concept prependable = + front_pushable || + detail::has_begin_emplace || + detail::has_begin_insert || + (sizeof...(Args) == 0 && default_prependable); + +namespace detail { + +template +concept front_emplace_returns = + has_emplace_front && + !std::is_void_v().emplace_front(std::declval()...))>; + +template +concept default_front_emplace_front_accessible = + !NeedReturn || + front_emplace_returns || + front_accessible || + ( + !has_emplace_front && + ( + has_begin_emplace || + ( + !has_push_front> && + has_begin_insert> + ) + ) + ); + +template +concept front_emplace_front_accessible = + !NeedReturn || + front_emplace_returns || + front_accessible || + ( + !has_emplace_front && + !has_push_front && + (has_begin_emplace || has_begin_insert) + ); + +template +struct prepend_fn +{ + template + requires default_prependable && default_front_emplace_front_accessible + static constexpr decltype(auto) + operator()(ContainerT& cont) + { + if constexpr (has_emplace_front) { + if constexpr (NeedReturn) { + if constexpr (std::is_void_v) { + cont.emplace_front(); + return front(cont); + } else { + return cont.emplace_front(); + } + } else { + (void)cont.emplace_front(); + } + } else { + using ValueT = std::ranges::range_value_t; + if constexpr (has_begin_emplace) { // prefer most ergonomic insertion for immovable types + if constexpr (NeedReturn) { + return *cont.emplace(std::ranges::begin(cont)); + } else { + (void)cont.emplace(std::ranges::begin(cont)); + } + } else if constexpr (has_push_front) { + cont.push_front(ValueT{}); + if constexpr (NeedReturn) { + return front(cont); + } + } else if constexpr (has_begin_insert) { + if constexpr (NeedReturn) { + return *cont.insert(std::ranges::begin(cont), ValueT{}); + } else { + (void)cont.insert(std::ranges::begin(cont), ValueT{}); + } + } else { + static_assert(false); + } + } + } + + template + requires prependable && front_emplace_front_accessible + static constexpr decltype(auto) + operator()(ContainerT& cont, FirstT&& first, Rest&&... rest) + { + if constexpr (has_emplace_front) { + if constexpr (NeedReturn) { + if constexpr (std::is_void_v(first), std::forward(rest)...))>) { + cont.emplace_front(std::forward(first), std::forward(rest)...); + return front(cont); + } else { + return cont.emplace_front(std::forward(first), std::forward(rest)...); + } + } else { + (void)cont.emplace_front(std::forward(first), std::forward(rest)...); + } + } else { + if constexpr (has_push_front) { + static_assert(sizeof...(Rest) == 0); + cont.push_front(std::forward(first)); + if constexpr (NeedReturn) { + return front(cont); + } + } else if constexpr (has_begin_emplace) { + if constexpr (NeedReturn) { + return *cont.emplace(std::ranges::begin(cont), std::forward(first), std::forward(rest)...); + } else { + (void)cont.emplace(std::ranges::begin(cont), std::forward(first), std::forward(rest)...); + } + } else if constexpr (has_begin_insert) { + static_assert(sizeof...(Rest) == 0); + if constexpr (NeedReturn) { + return *cont.insert(std::ranges::begin(cont), std::forward(first)); + } else { + (void)cont.insert(std::ranges::begin(cont), std::forward(first)); + } + } else { + static_assert(false); + } + } + } +}; + +} // detail + +[[maybe_unused]] inline constexpr detail::prepend_fn prepend_return{}; +[[maybe_unused]] inline constexpr detail::prepend_fn prepend{}; + + +// ------------------------------------------------------------ + +namespace detail { + +template +concept has_emplace_back = + requires(ContainerT& cont) { + cont.emplace_back(std::declval()...); + }; + +template +concept has_push_back = + sizeof...(Args) == 1 && + requires(ContainerT& cont) { + cont.push_back(std::declval()...); + }; + +template +concept has_end_emplace = + std::ranges::range && + requires(ContainerT& cont) { + cont.emplace(std::ranges::end(cont), std::declval()...); + }; + +template +concept has_end_insert = + sizeof...(Args) == 1 && + std::ranges::range && + requires(ContainerT& cont) { + cont.insert(std::ranges::end(cont), std::declval()...); + }; + +} // detail + +template +concept back_pushable = + detail::has_emplace_back || + detail::has_push_back; + +template +concept default_appendable = + std::ranges::range && + std::default_initializable> && + ( + detail::has_emplace_back || detail::has_end_emplace || + ( + std::move_constructible> && + ( + detail::has_push_back> || + detail::has_end_insert> + ) + ) + ); + +template +concept appendable = + back_pushable || + detail::has_end_emplace || + detail::has_end_insert || + (sizeof...(Args) == 0 && default_appendable); + +namespace detail { + +template +concept back_emplace_returns = + has_emplace_back && + !std::is_void_v().emplace_back(std::declval()...))>; + +template +concept default_back_emplace_back_accessible = + !NeedReturn || + back_emplace_returns || + back_accessible || + ( + !has_emplace_back && + ( + has_end_emplace || + ( + !has_push_back> && + has_end_insert> + ) + ) + ); + +template +concept back_emplace_back_accessible = + !NeedReturn || + back_emplace_returns || + back_accessible || + ( + !has_emplace_back && + !has_push_back && + (has_end_emplace || has_end_insert) + ); + +template +struct append_fn +{ + template + requires default_appendable && default_back_emplace_back_accessible + static constexpr decltype(auto) + operator()(ContainerT& cont) + { + if constexpr (has_emplace_back) { + if constexpr (NeedReturn) { + if constexpr (std::is_void_v) { + cont.emplace_back(); + return back(cont); + } else { + return cont.emplace_back(); + } + } else { + (void)cont.emplace_back(); + } + } else { + using ValueT = std::ranges::range_value_t; + if constexpr (has_end_emplace) { // prefer most ergonomic insertion for immovable types + if constexpr (NeedReturn) { + return *cont.emplace(std::ranges::end(cont)); + } else { + (void)cont.emplace(std::ranges::end(cont)); + } + } else if constexpr (has_push_back) { + cont.push_back(ValueT{}); + if constexpr (NeedReturn) { + return back(cont); + } + } else if constexpr (has_end_insert) { + if constexpr (NeedReturn) { + return *cont.insert(std::ranges::end(cont), ValueT{}); + } else { + (void)cont.insert(std::ranges::end(cont), ValueT{}); + } + } else { + static_assert(false); + } + } + } + + template + requires appendable && back_emplace_back_accessible + static constexpr decltype(auto) + operator()(ContainerT& cont, FirstT&& first, Rest&&... rest) + { + if constexpr (has_emplace_back) { + if constexpr (NeedReturn) { + if constexpr (std::is_void_v(first), std::forward(rest)...))>) { + cont.emplace_back(std::forward(first), std::forward(rest)...); + return back(cont); + } else { + return cont.emplace_back(std::forward(first), std::forward(rest)...); + } + } else { + (void)cont.emplace_back(std::forward(first), std::forward(rest)...); + } + } else { + if constexpr (has_push_back) { + static_assert(sizeof...(Rest) == 0); + cont.push_back(std::forward(first)); + if constexpr (NeedReturn) { + return back(cont); + } + } else if constexpr (has_end_emplace) { + if constexpr (NeedReturn) { + return *cont.emplace(std::ranges::end(cont), std::forward(first), std::forward(rest)...); + } else { + (void)cont.emplace(std::ranges::end(cont), std::forward(first), std::forward(rest)...); + } + } else if constexpr (has_end_insert) { + static_assert(sizeof...(Rest) == 0); + if constexpr (NeedReturn) { + return *cont.insert(std::ranges::end(cont), std::forward(first)); + } else { + (void)cont.insert(std::ranges::end(cont), std::forward(first)); + } + } else { + static_assert(false); + } + } + } +}; + +} // detail + +[[maybe_unused]] inline constexpr detail::append_fn append_return{}; +[[maybe_unused]] inline constexpr detail::append_fn append{}; + +// ------------------------------------------------------------ + +namespace detail { + +template +concept begin_erasable = + std::ranges::range && + requires(ContainerT& cont) { + cont.erase(std::ranges::begin(cont)); + }; + +template +concept end_erasable = + std::ranges::bidirectional_range && + std::ranges::common_range && + requires(ContainerT& cont) { + cont.erase(std::ranges::prev(std::ranges::end(cont))); + }; + +template +concept front_poppable = requires(ContainerT& cont) { + cont.pop_front(); +}; + +template +concept back_poppable = requires(ContainerT& cont) { + cont.pop_back(); +}; + +struct erase_front_fn +{ + template + requires front_poppable + static constexpr void operator()(ContainerT& cont) + noexcept(noexcept(cont.pop_front())) + { + cont.pop_front(); + } + + template + requires (!front_poppable) && begin_erasable + static constexpr void operator()(ContainerT& cont) + noexcept(noexcept(cont.erase(std::ranges::begin(cont)))) + { + cont.erase(std::ranges::begin(cont)); + } +}; + +struct erase_back_fn +{ + template + requires back_poppable + static constexpr void operator()(ContainerT& cont) + noexcept(noexcept(cont.pop_back())) + { + cont.pop_back(); + } + + template + requires (!back_poppable) && end_erasable + static constexpr void operator()(ContainerT& cont) + noexcept(noexcept(cont.erase(std::ranges::prev(std::ranges::end(cont))))) + { + cont.erase(std::ranges::prev(std::ranges::end(cont))); + } +}; + +} // detail + +[[maybe_unused]] inline constexpr detail::erase_front_fn erase_front{}; +[[maybe_unused]] inline constexpr detail::erase_back_fn erase_back{}; + +template concept front_erasable = requires(ContainerT& cont) { erase_front(cont); }; +template concept back_erasable = requires(ContainerT& cont) { erase_back(cont); }; + + +// ------------------------------------------------------------ + +template +concept growable_array = + std::ranges::range && + ( + ( + std::default_initializable> && + appendable + ) || + ( + std::move_constructible> && + appendable> + ) + ); + +template +concept fixed_array = + !growable_array && + std::ranges::output_range> && + std::ranges::sized_range; + +// ------------------------------------------------------------ + +template +concept has_reserve = requires(std::remove_cvref_t& cont, SizeT const size) { + cont.reserve(size); +}; + +template +concept has_shrink_to_fit = requires(std::remove_cvref_t& cont) { + cont.shrink_to_fit(); +}; + + +template +concept compatible_iterator = + // The standard only requires "not participating unless `It` qualifies as an input + // iterator" and leaves the extent unspecified; implementations check as little as + // `iterator_traits::iterator_category`. The iterators that satisfy Cpp17InputIterator + // but not `std::input_iterator` are legacy defects (e.g. `difference_type = void`), + // which we do not support, so `std::input_iterator` is used here. + std::input_iterator && + // This is NOT `std::convertible_to`; see: https://stackoverflow.com/questions/79940611/stdfrom-range-does-not-work-with-explicit-conversions?noredirect=1 + std::constructible_from>; + +// Note: `compatible_range` does NOT subsume `compatible_iterator` by design. +// Historically in the C++ standard, `container(std::from_range_t, R)` and `container(it, se)` +// meant two distinct semantics. For details, see the comment on `compatible_iterator`. +// +// Also note that in the standard, the element requirement of `container(it, se)` is a +// precondition (Cpp17EmplaceConstructible; violation is a hard error inside the constructor), +// whereas `compatible_iterator` makes it a constraint. This keeps +// `std::constructible_from` honest, which generic code such as +// `std::ranges::to` relies on when choosing a construction strategy. +template +concept compatible_range = + std::ranges::input_range && + std::convertible_to, ElemT>; + +} // iris::container + +namespace iris::container::dummy { + +template +struct mapping_container +{ + using key_type = K; + using mapped_type = V; + std::pair const* begin() const; + std::pair const* end() const; + + mapping_container() = default; + mapping_container(mapping_container const&) = delete; + mapping_container(mapping_container&&) = delete; + mapping_container& operator=(mapping_container const&) = delete; + mapping_container& operator=(mapping_container&&) = delete; + + void emplace(K&&, V&&); +}; + +template +struct unique_mapping_container +{ + using key_type = K; + using mapped_type = V; + std::pair const* begin() const; + std::pair const* end() const; + + unique_mapping_container() = default; + unique_mapping_container(unique_mapping_container const&) = delete; + unique_mapping_container(unique_mapping_container&&) = delete; + unique_mapping_container& operator=(unique_mapping_container const&) = delete; + unique_mapping_container& operator=(unique_mapping_container&&) = delete; + + void emplace(K&&, V&&); + std::pair const*, bool> try_emplace(K&&, V&&); + std::pair const*, bool> insert_or_assign(K&&, V&&); +}; + +template +struct growable_array +{ + T const* begin() const; + T const* end() const; + void emplace_back(T&&); + void emplace_back() requires std::default_initializable; +}; + +template +using fixed_array = T[1]; + +} // iris::container::dummy + +#endif diff --git a/include/iris/error/throwf.hpp b/include/iris/error/throwf.hpp new file mode 100644 index 0000000..eac5e0c --- /dev/null +++ b/include/iris/error/throwf.hpp @@ -0,0 +1,50 @@ +#ifndef IRIS_ZZ_ERROR_THROWF_HPP +#define IRIS_ZZ_ERROR_THROWF_HPP + +#include // IWYU pragma: keep + +#include // IWYU pragma: export +#include // IWYU pragma: export +#include +#include + +#ifndef IRIS_CONFIG_THROW_IMPL +# define IRIS_CONFIG_THROW_IMPL(...) throw __VA_ARGS__ +#endif + +#ifndef IRIS_CONFIG_THROW_NORETURN +# define IRIS_CONFIG_THROW_NORETURN [[noreturn]] +#endif + +namespace iris { + +inline namespace error_functions { + +// This function can be used to strongly assume optimization in some performance- +// critical paths as some compilers fail to optimize the plain `throw` statement +// even though the statement itself should imply `[[noreturn]]`. +template +IRIS_CONFIG_THROW_NORETURN void throwf() +{ + static_assert(std::is_base_of_v); + static_assert(std::is_constructible_v); + IRIS_CONFIG_THROW_IMPL(E{}); +} + +// This function can be used to strongly assume optimization in some performance- +// critical paths as some compilers fail to optimize the plain `throw` statement +// even though the statement itself should imply `[[noreturn]]`. +template + requires std::is_constructible_v +IRIS_CONFIG_THROW_NORETURN void throwf(Arg&& arg, Rest&&... rest) +{ + static_assert(std::is_base_of_v); + static_assert(!std::is_base_of_v>, "don't copy/move construct exception types directly"); + IRIS_CONFIG_THROW_IMPL(E{std::forward(arg), std::forward(rest)...}); +} + +} // error_functions + +} // iris + +#endif diff --git a/include/iris/error/throwf_format.hpp b/include/iris/error/throwf_format.hpp new file mode 100644 index 0000000..0403227 --- /dev/null +++ b/include/iris/error/throwf_format.hpp @@ -0,0 +1,64 @@ +#ifndef IRIS_ZZ_ERROR_THROWF_FORMAT_HPP +#define IRIS_ZZ_ERROR_THROWF_FORMAT_HPP + +// SPDX-License-Identifier: MIT + +#include // IWYU pragma: keep +#include // IWYU pragma: export +#include + +#include // IWYU pragma: export +#include +#include +#include +#include +#include + +namespace iris { + +namespace detail { + +template +concept constructible_from_string_like_types = + std::is_constructible_v || + std::is_constructible_v || + std::is_constructible_v; + +} // detail + +inline namespace error_functions { + +template + requires detail::constructible_from_string_like_types +IRIS_CONFIG_THROW_NORETURN void throwf(std::format_string fmt, Args&&... args) +{ + static_assert(std::is_base_of_v); + IRIS_CONFIG_THROW_IMPL(E{std::format(std::move(fmt), std::forward(args)...)}); +} + +template + requires detail::constructible_from_string_like_types +IRIS_CONFIG_THROW_NORETURN void throwf(Arg0&& arg0, std::format_string fmt, Args&&... args) +{ + static_assert(std::is_base_of_v); + IRIS_CONFIG_THROW_IMPL(E{std::forward(arg0), std::format(std::move(fmt), std::forward(args)...)}); +} + +template + requires detail::constructible_from_string_like_types +IRIS_CONFIG_THROW_NORETURN void throwf(Arg0&& arg0, Arg1&& arg1, std::format_string fmt, Args&&... args) +{ + static_assert(std::is_base_of_v); + IRIS_CONFIG_THROW_IMPL( + E{ + std::forward(arg0), std::forward(arg1), + std::format(std::move(fmt), std::forward(args)...) + } + ); +} + +} // error_functions + +} // iris + +#endif diff --git a/include/iris/exception.hpp b/include/iris/exception.hpp index 058644d..990b788 100644 --- a/include/iris/exception.hpp +++ b/include/iris/exception.hpp @@ -1,88 +1,14 @@ #ifndef IRIS_ZZ_EXCEPTION_HPP #define IRIS_ZZ_EXCEPTION_HPP -#include // IWYU pragma: keep - -#include - -#include -#include -#include -#include -#include -#include -#include - -#ifndef IRIS_CONFIG_THROW_IMPL -# define IRIS_CONFIG_THROW_IMPL(...) throw __VA_ARGS__ -#endif - -#ifndef IRIS_CONFIG_THROW_NORETURN -# define IRIS_CONFIG_THROW_NORETURN [[noreturn]] -#endif - -namespace iris { - -namespace detail { +// SPDX-License-Identifier: MIT -template -concept constructible_from_string_like_types = - std::is_constructible_v || - std::is_constructible_v || - std::is_constructible_v; - -} // detail - -inline namespace error_functions { - -template -IRIS_CONFIG_THROW_NORETURN void throwf() -{ - static_assert(std::is_base_of_v); - static_assert(std::is_constructible_v); - IRIS_CONFIG_THROW_IMPL(E{}); -} - -template - requires std::is_constructible_v -IRIS_CONFIG_THROW_NORETURN void throwf(Arg&& arg, Rest&&... rest) -{ - static_assert(std::is_base_of_v); - static_assert(!std::is_base_of_v>, "don't copy/move construct exception types directly"); - IRIS_CONFIG_THROW_IMPL(E{std::forward(arg), std::forward(rest)...}); -} - -template - requires detail::constructible_from_string_like_types -IRIS_CONFIG_THROW_NORETURN void throwf(std::format_string fmt, Args&&... args) -{ - static_assert(std::is_base_of_v); - IRIS_CONFIG_THROW_IMPL(E{std::format(std::move(fmt), std::forward(args)...)}); -} - -template - requires detail::constructible_from_string_like_types -IRIS_CONFIG_THROW_NORETURN void throwf(Arg0&& arg0, std::format_string fmt, Args&&... args) -{ - static_assert(std::is_base_of_v); - IRIS_CONFIG_THROW_IMPL(E{std::forward(arg0), std::format(std::move(fmt), std::forward(args)...)}); -} - -template - requires detail::constructible_from_string_like_types -IRIS_CONFIG_THROW_NORETURN void throwf(Arg0&& arg0, Arg1&& arg1, std::format_string fmt, Args&&... args) -{ - static_assert(std::is_base_of_v); - IRIS_CONFIG_THROW_IMPL( - E{ - std::forward(arg0), std::forward(arg1), - std::format(std::move(fmt), std::forward(args)...) - } - ); -} +#include // IWYU pragma: keep -} // error_functions +#include // IWYU pragma: export +#include // IWYU pragma: export -} // iris +#include // IWYU pragma: export +#include // IWYU pragma: export #endif diff --git a/include/iris/indexed_value.hpp b/include/iris/indexed_value.hpp new file mode 100644 index 0000000..a530993 --- /dev/null +++ b/include/iris/indexed_value.hpp @@ -0,0 +1,506 @@ +#ifndef IRIS_ZZ_INDEXED_VALUE_HPP +#define IRIS_ZZ_INDEXED_VALUE_HPP + +// SPDX-License-Identifier: MIT + +#include // IWYU pragma: keep +#include +#include + +#include + +#include +#include +#include +#include + +#include + +namespace iris { + +template +struct indexed_value +{ + using index_type = IndexT; + using value_type = T; + + IndexT index; + T value; + + // ---------------------------------------------------- + + template + requires + std::convertible_to && + std::convertible_to + constexpr operator indexed_value() & + noexcept( + noexcept(static_cast(std::declval())) && + noexcept(static_cast(std::declval())) + ) + { + return { + static_cast(index), + static_cast(value) + }; + } + + template + requires + std::convertible_to && + std::convertible_to + constexpr operator indexed_value() const& + noexcept( + noexcept(static_cast(std::declval())) && + noexcept(static_cast(std::declval())) + ) + { + return { + static_cast(index), + static_cast(value) + }; + } + + template + requires + std::convertible_to && + std::convertible_to + constexpr operator indexed_value() && + noexcept( + noexcept(static_cast(std::declval())) && + noexcept(static_cast(std::declval())) + ) + { + return { + static_cast(static_cast(index)), + static_cast(static_cast(value)) + }; + } + + template + requires + std::convertible_to && + std::convertible_to + constexpr operator indexed_value() const&& + noexcept( + noexcept(static_cast(std::declval())) && + noexcept(static_cast(std::declval())) + ) + { + return { + static_cast(static_cast(index)), + static_cast(static_cast(value)) + }; + } + + // ---------------------------------------------------- + + template + requires + std::convertible_to && + std::convertible_to + constexpr operator std::tuple() & + noexcept( + noexcept(static_cast(std::declval())) && + noexcept(static_cast(std::declval())) + ) + { + return std::tuple( + static_cast(index), + static_cast(value) + ); + } + + template + requires + std::convertible_to && + std::convertible_to + constexpr operator std::tuple() const& + noexcept( + noexcept(static_cast(std::declval())) && + noexcept(static_cast(std::declval())) + ) + { + return std::tuple( + static_cast(index), + static_cast(value) + ); + } + + template + requires + std::convertible_to && + std::convertible_to + constexpr operator std::tuple() && + noexcept( + noexcept(static_cast(std::declval())) && + noexcept(static_cast(std::declval())) + ) + { + return std::tuple( + static_cast(static_cast(index)), + static_cast(static_cast(value)) + ); + } + + template + requires + std::convertible_to && + std::convertible_to + constexpr operator std::tuple() const&& + noexcept( + noexcept(static_cast(std::declval())) && + noexcept(static_cast(std::declval())) + ) + { + return std::tuple( + static_cast(static_cast(index)), + static_cast(static_cast(value)) + ); + } + + // ---------------------------------------------------- + + template + requires + std::convertible_to && + std::convertible_to + constexpr operator std::pair() & + noexcept( + noexcept(static_cast(std::declval())) && + noexcept(static_cast(std::declval())) + ) + { + return std::pair( + static_cast(index), + static_cast(value) + ); + } + + template + requires + std::convertible_to && + std::convertible_to + constexpr operator std::pair() const& + noexcept( + noexcept(static_cast(std::declval())) && + noexcept(static_cast(std::declval())) + ) + { + return std::pair( + static_cast(index), + static_cast(value) + ); + } + + template + requires + std::convertible_to && + std::convertible_to + constexpr operator std::pair() && + noexcept( + noexcept(static_cast(std::declval())) && + noexcept(static_cast(std::declval())) + ) + { + return std::pair( + static_cast(static_cast(index)), + static_cast(static_cast(value)) + ); + } + + template + requires + std::convertible_to && + std::convertible_to + constexpr operator std::pair() const&& + noexcept( + noexcept(static_cast(std::declval())) && + noexcept(static_cast(std::declval())) + ) + { + return std::pair( + static_cast(static_cast(index)), + static_cast(static_cast(value)) + ); + } +}; + +template + requires req::half_equality_comparable && req::half_equality_comparable +[[nodiscard]] constexpr bool operator==(indexed_value const& a, indexed_value const& b) + noexcept(noexcept(a.index == b.index) && noexcept(a.value == b.value)) +{ + return a.index == b.index && a.value == b.value; +} + +template + requires + (!is_ttp_specialization_of_v) && + (std::tuple_size>::value == 2) && + req::half_equality_comparable>> && + req::half_equality_comparable>> +[[nodiscard]] constexpr bool operator==(indexed_value const& a, U const& b) +{ + return a.index == std::get<0>(b) && a.value == std::get<1>(b); +} + +template + requires requires (IndexT const& i, IndexU const& j, T const& t, U const& u) { + cmp::synth_three_way{}(i, j); + cmp::synth_three_way{}(t, u); + } +[[nodiscard]] constexpr auto operator<=>(indexed_value const& a, indexed_value const& b) + noexcept( + noexcept(cmp::synth_three_way{}(a.index, b.index)) && + noexcept(cmp::synth_three_way{}(a.value, b.value)) + ) + -> std::common_comparison_category_t< + cmp::synth_three_way_result, + cmp::synth_three_way_result + > +{ + if (auto const comp = cmp::synth_three_way{}(a.index, b.index); comp != 0) { + return comp; + } + return cmp::synth_three_way{}(a.value, b.value); +} + +template +[[nodiscard]] constexpr decltype(auto) get(indexed_value& elem) noexcept +{ + static_assert(I < 2); + if constexpr (I == 0) { return (elem.index); } + else if constexpr (I == 1) { return (elem.value); } +} + +template +[[nodiscard]] constexpr decltype(auto) get(indexed_value const& elem) noexcept +{ + static_assert(I < 2); + if constexpr (I == 0) { return (elem.index); } + else if constexpr (I == 1) { return (elem.value); } +} + +template +[[nodiscard]] constexpr decltype(auto) get(indexed_value&& elem) noexcept +{ + static_assert(I < 2); + if constexpr (I == 0) { return static_cast(elem.index); } + else if constexpr (I == 1) { return static_cast(elem.value); } +} + +template +[[nodiscard]] constexpr decltype(auto) get(indexed_value const&& elem) noexcept +{ + static_assert(I < 2); + if constexpr (I == 0) { return static_cast(elem.index); } + else if constexpr (I == 1) { return static_cast(elem.value); } +} + +} // iris + +template +struct std::tuple_size> + : std::integral_constant +{}; + +template +struct std::tuple_element<0, iris::indexed_value> +{ + using type = IndexT; +}; +template +struct std::tuple_element<1, iris::indexed_value> +{ + using type = T; +}; + + +// indexed_value + indexed_value +template + requires requires { + typename std::common_type_t; + typename std::common_type_t; + } +struct std::common_type< + iris::indexed_value, + iris::indexed_value +> +{ + using type = iris::indexed_value< + std::common_type_t, + std::common_type_t + >; +}; + +// indexed_value + indexed_value +template< + class IndexT, class T, class IndexU, class U, + template class TQual, template class UQual +> + requires requires { + typename std::common_reference_t, UQual>; + typename std::common_reference_t, UQual>; + } +struct std::basic_common_reference< + iris::indexed_value, iris::indexed_value, TQual, UQual +> +{ + using type = iris::indexed_value< + std::common_reference_t, UQual>, + std::common_reference_t, UQual> + >; +}; + +// ----------------------------------------------------------- + +// indexed_value + std::tuple +template + requires requires { + typename std::common_type_t; + typename std::common_type_t; + } +struct std::common_type< + iris::indexed_value, + std::tuple +> +{ + using type = std::tuple< + std::common_type_t, + std::common_type_t + >; +}; + +// std::tuple + indexed_value +template + requires requires { + typename std::common_type_t; + typename std::common_type_t; + } +struct std::common_type< + std::tuple, + iris::indexed_value +> +{ + using type = std::tuple< + std::common_type_t, + std::common_type_t + >; +}; + + +// indexed_value + std::tuple +template< + class IndexT, class T, class Tup0, class Tup1, + template class TQual, template class UQual +> + requires requires { + typename std::common_reference_t, UQual>; + typename std::common_reference_t, UQual>; + } +struct std::basic_common_reference< + iris::indexed_value, std::tuple, TQual, UQual +> +{ + using type = std::tuple< + std::common_reference_t, UQual>, + std::common_reference_t, UQual> + >; +}; + +// std::tuple + indexed_value +template< + class Tup0, class Tup1, class IndexU, class U, + template class TQual, template class UQual +> + requires requires { + typename std::common_reference_t, UQual>; + typename std::common_reference_t, UQual>; + } +struct std::basic_common_reference< + std::tuple, iris::indexed_value, TQual, UQual +> +{ + using type = std::tuple< + std::common_reference_t, UQual>, + std::common_reference_t, UQual> + >; +}; + +// ----------------------------------------------------------- + +// indexed_value + std::pair +template + requires requires { + typename std::common_type_t; + typename std::common_type_t; + } +struct std::common_type< + iris::indexed_value, + std::pair +> +{ + using type = std::pair< + std::common_type_t, + std::common_type_t + >; +}; + +// std::pair + indexed_value +template + requires requires { + typename std::common_type_t; + typename std::common_type_t; + } +struct std::common_type< + std::pair, + iris::indexed_value +> +{ + using type = std::pair< + std::common_type_t, + std::common_type_t + >; +}; + + +// indexed_value + std::pair +template< + class IndexT, class T, class Tup0, class Tup1, + template class TQual, template class UQual +> + requires requires { + typename std::common_reference_t, UQual>; + typename std::common_reference_t, UQual>; + } +struct std::basic_common_reference< + iris::indexed_value, std::pair, TQual, UQual +> +{ + using type = std::pair< + std::common_reference_t, UQual>, + std::common_reference_t, UQual> + >; +}; + +// std::pair + indexed_value +template< + class Tup0, class Tup1, class IndexU, class U, + template class TQual, template class UQual +> + requires requires { + typename std::common_reference_t, UQual>; + typename std::common_reference_t, UQual>; + } +struct std::basic_common_reference< + std::pair, iris::indexed_value, TQual, UQual +> +{ + using type = std::pair< + std::common_reference_t, UQual>, + std::common_reference_t, UQual> + >; +}; + +#endif diff --git a/include/iris/interval.hpp b/include/iris/interval.hpp index 7d55521..4c95191 100644 --- a/include/iris/interval.hpp +++ b/include/iris/interval.hpp @@ -21,7 +21,7 @@ namespace iris { -template +template struct interval { using value_type = T; @@ -61,7 +61,7 @@ struct interval // Returns `true` if `other` shares any point with `*this`. // Note 1: intersects(∅) always returns `false`. // Note 2: Adjacent-only contact is touches(); for intersects-or-touches use connected(). - template + template [[nodiscard]] constexpr bool intersects(interval const other) const noexcept { return (lower < other.upper && other.lower < upper) && !empty() && !other.empty(); @@ -69,7 +69,7 @@ struct interval // !intersects // Note: disjoint(∅) always returns `true`. - template + template [[nodiscard]] constexpr bool disjoint(interval const other) const noexcept { return (upper <= other.lower || other.upper <= lower) || empty() || other.empty(); @@ -77,7 +77,7 @@ struct interval // Closures meet but the sets share no point. // Note: touches(∅) always returns `false`. - template + template [[nodiscard]] constexpr bool touches(interval const other) const noexcept { return (upper == other.lower || other.upper == lower) && !empty() && !other.empty(); @@ -85,7 +85,7 @@ struct interval // intersects || touches // Note: connected(∅) always returns `false`. - template + template [[nodiscard]] constexpr bool connected(interval const other) const noexcept { return (lower <= other.upper && other.lower <= upper) && !empty() && !other.empty(); @@ -94,7 +94,7 @@ struct interval // Returns `true` if every point of `other` is a point of `*this`. // Note: covers(∅) always returns `true`. // See also: `encloses(other)`. - template + template [[nodiscard]] constexpr bool covers(interval const other) const noexcept { return (lower <= other.lower && other.upper <= upper) || other.empty(); @@ -103,7 +103,7 @@ struct interval // Returns `true` if `other`'s bounds lie within [lower, upper]. // For nonempty `other`: identical to `covers(other)`. // For empty `other`: position-respecting (treats it like a 0-length "text caret".) - template + template [[nodiscard]] constexpr bool encloses(interval const other) const noexcept { return lower <= other.lower && other.upper <= upper; @@ -134,7 +134,7 @@ struct interval // Returns `true` if both intervals have exactly same bounds. // Note: All empty intervals denote ∅ and are mutually equal regardless of // bounds. Differs from `operator==`, which compares data representations. - template + template [[nodiscard]] constexpr bool equals(interval const other) const noexcept { return (lower == other.lower && upper == other.upper) || (empty() && other.empty()); @@ -143,14 +143,14 @@ struct interval // ------------------------------------------- // A ∩ B. Result is canonical empty [0,0) when the sets share no point. - template + template [[nodiscard]] constexpr interval intersection(interval const other) const noexcept { auto const lo = std::max(lower, static_cast(other.lower)); auto const hi = std::min(upper, static_cast(other.upper)); return lo < hi ? interval{lo, hi} : interval{}; } - template + template [[nodiscard]] constexpr interval operator&(interval const other) const noexcept { return intersection(other); diff --git a/include/iris/interval_set.hpp b/include/iris/interval_set.hpp index 8746d73..d7797e6 100644 --- a/include/iris/interval_set.hpp +++ b/include/iris/interval_set.hpp @@ -4,7 +4,6 @@ // SPDX-License-Identifier: MIT #include // IWYU pragma: keep -#include #include #include @@ -29,20 +28,24 @@ class interval_set using map_type = MapT; using offset_type = IntervalT::value_type; - class const_iterator : public iterator_base + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + + class const_iterator { - using typename iterator_base::iterator_base_type; - static_assert(std::bidirectional_iterator); - iterator_base_type it_; + map_type::const_iterator it_; public: + using iterator_concept = std::bidirectional_iterator_tag; + using iterator_category = std::bidirectional_iterator_tag; + using value_type = IntervalT; - using pointer = IntervalT const*; using reference = IntervalT; + using difference_type = interval_set::difference_type; constexpr const_iterator() noexcept = default; - constexpr explicit const_iterator(iterator_base_type it) noexcept + constexpr explicit const_iterator(map_type::const_iterator it) noexcept : it_(std::move(it)) {} diff --git a/include/iris/iterator.hpp b/include/iris/iterator.hpp index 47aecc7..51d5edc 100644 --- a/include/iris/iterator.hpp +++ b/include/iris/iterator.hpp @@ -6,64 +6,43 @@ #include // IWYU pragma: keep #include -#include +#include +#include namespace iris { -template -struct iterator_tags_base; - -template - requires requires { - typename std::iterator_traits::iterator_category; - typename std::iterator_traits::iterator_concept; - } -struct iterator_tags_base -{ - using iterator_base_type = It; - using iterator_category = std::iterator_traits::iterator_category; - using iterator_concept = std::iterator_traits::iterator_concept; - - [[nodiscard]] constexpr bool operator==(iterator_tags_base const&) const noexcept = default; - [[nodiscard]] constexpr std::strong_ordering operator<=>(iterator_tags_base const&) const noexcept = default; -}; - -template - requires - requires { typename std::iterator_traits::iterator_category; } && - (!requires { typename std::iterator_traits::iterator_concept; }) -struct iterator_tags_base -{ - using iterator_base_type = It; - using iterator_category = std::iterator_traits::iterator_category; - - [[nodiscard]] constexpr bool operator==(iterator_tags_base const&) const noexcept = default; - [[nodiscard]] constexpr std::strong_ordering operator<=>(iterator_tags_base const&) const noexcept = default; -}; - -template - requires - (!requires { typename std::iterator_traits::iterator_category; }) && - requires { typename std::iterator_traits::iterator_concept; } -struct iterator_tags_base -{ - using iterator_base_type = It; - using iterator_concept = std::iterator_traits::iterator_concept; - - [[nodiscard]] constexpr bool operator==(iterator_tags_base const&) const noexcept = default; - [[nodiscard]] constexpr std::strong_ordering operator<=>(iterator_tags_base const&) const noexcept = default; -}; - -// ---------------------------------------------- - -template -struct iterator_base : iterator_tags_base -{ - using difference_type = std::iterator_traits::difference_type; - - [[nodiscard]] constexpr bool operator==(iterator_base const&) const noexcept = default; - [[nodiscard]] constexpr std::strong_ordering operator<=>(iterator_base const&) const noexcept = default; -}; +namespace detail { + +template +std::input_iterator_tag iter_concept_of(); + +template +std::forward_iterator_tag iter_concept_of(); + +template +std::bidirectional_iterator_tag iter_concept_of(); + +template +std::random_access_iterator_tag iter_concept_of(); + +template +std::contiguous_iterator_tag iter_concept_of(); + +} // detail + +template +using iter_concept_t = decltype(detail::iter_concept_of()); + +template +using iter_cat_t = std::iterator_traits::iterator_category; + +template +concept has_iter_cat = requires { typename iter_cat_t; }; + +// min(Tag, Limit) on the standard tag hierarchy +template + requires std::derived_from || std::derived_from +using clamp_iter_tag_t = std::conditional_t, Limit, Tag>; } // iris diff --git a/include/iris/marshal/serialize.hpp b/include/iris/marshal/serialize.hpp index ef026bc..62f2b4a 100644 --- a/include/iris/marshal/serialize.hpp +++ b/include/iris/marshal/serialize.hpp @@ -5,6 +5,9 @@ #include // IWYU pragma: keep +#include +#include + #include #include @@ -207,7 +210,7 @@ struct basic_load_fn } template R> - requires ranges::growable_array_writable + requires container::growable_array static constexpr void operator()(ReaderT& rd, R& arr) { R tmp{}; @@ -216,8 +219,8 @@ struct basic_load_fn std::ranges::range_value_t elem{}; basic_load_fn{}(elem_rd, elem); - if constexpr (ranges::back_emplaceable) { - tmp.emplace_back(std::move(elem)); + if constexpr (container::back_pushable) { + container::append(tmp, std::move(elem)); } else { tmp.emplace(std::move(elem)); } @@ -231,7 +234,7 @@ struct basic_load_fn } template R> - requires ranges::fixed_array_writable + requires container::fixed_array static constexpr void operator()(ReaderT& rd, R& arr) { auto it = std::ranges::begin(arr); @@ -274,7 +277,7 @@ struct basic_load_fn mapped_type v{}; basic_load_fn{}(member_rd, v); - if constexpr (ranges::unique_mapping_container) { + if constexpr (container::unique_mapping_container) { if (!tmp.try_emplace(std::move(k), std::move(v)).second) { throw load_error{"object: duplicate key"}; } diff --git a/include/iris/marshal/serialize_traits.hpp b/include/iris/marshal/serialize_traits.hpp index ee098f5..3001eca 100644 --- a/include/iris/marshal/serialize_traits.hpp +++ b/include/iris/marshal/serialize_traits.hpp @@ -11,6 +11,7 @@ #include #include +#include #include @@ -313,16 +314,16 @@ consteval bool is_deserializable_impl() } else if constexpr (ranges::mapping_range) { return - ranges::mapping_container && + container::mapping_container && Format::template loadable_key> && loadable, Format> && loadable, Format>; } else if constexpr (std::ranges::input_range) { - if constexpr (ranges::growable_array_writable) { + if constexpr (container::growable_array) { return loadable, Format>; - } else if constexpr (ranges::fixed_array_writable) { + } else if constexpr (container::fixed_array) { return is_deserializable_impl, Format>(); } else { diff --git a/include/iris/ranges.hpp b/include/iris/ranges.hpp index 89fd5a8..897a465 100644 --- a/include/iris/ranges.hpp +++ b/include/iris/ranges.hpp @@ -5,8 +5,10 @@ #include // IWYU pragma: keep +#include // IWYU pragma: keep #include +#include // IWYU pragma: keep #include // IWYU pragma: export #include #include @@ -36,14 +38,6 @@ concept key_value_range = gettable<0, std::ranges::range_reference_t> && gettable<1, std::ranges::range_reference_t>; -template -struct dummy_key_value_range -{ - std::pair const* begin() const; - std::pair const* end() const; - ~dummy_key_value_range() = delete; -}; - template using range_key_t = std::remove_cvref_t>>; @@ -64,16 +58,6 @@ concept mapping_range = std::same_as, typename std::remove_cvref_t::key_type> && std::same_as, typename std::remove_cvref_t::mapped_type>; -template -struct dummy_mapping_range -{ - using key_type = K; - using mapped_type = V; - std::pair const* begin() const; - std::pair const* end() const; - ~dummy_mapping_range() = delete; -}; - // Thin view that adds the map-specific trait to the underlying range. // Can be used for making `key_value_range` model `mapping_range`. @@ -130,118 +114,26 @@ inline constexpr bool std::ranges::enable_borrowed_range; -namespace iris::ranges { - -template -concept mapping_container = - mapping_range && - std::default_initializable> && - requires( - std::remove_cvref_t& c, - typename std::remove_cvref_t::key_type k, - typename std::remove_cvref_t::mapped_type v - ) { - c.emplace(std::move(k), std::move(v)); - }; +namespace iris::ranges::dummy { template -struct dummy_mapping_container +struct key_value_range { - using key_type = K; - using mapped_type = V; std::pair const* begin() const; std::pair const* end() const; - - dummy_mapping_container() = default; - dummy_mapping_container(dummy_mapping_container const&) = delete; - dummy_mapping_container(dummy_mapping_container&&) = delete; - dummy_mapping_container& operator=(dummy_mapping_container const&) = delete; - dummy_mapping_container& operator=(dummy_mapping_container&&) = delete; - - void emplace(K&&, V&&); + ~key_value_range() = delete; }; -template -concept unique_mapping_container = - mapping_container && - requires( - std::remove_cvref_t& c, - typename std::remove_cvref_t::key_type k, - typename std::remove_cvref_t::mapped_type v - ) { - { c.try_emplace(std::move(k), std::move(v)).second } -> std::convertible_to; - { c.insert_or_assign(std::move(k), std::move(v)).second } -> std::convertible_to; - }; - template -struct dummy_unique_mapping_container : dummy_mapping_container +struct mapping_range { using key_type = K; using mapped_type = V; std::pair const* begin() const; std::pair const* end() const; - - using dummy_mapping_container::dummy_mapping_container; - - std::pair const*, bool> try_emplace(K&&, V&&); - std::pair const*, bool> insert_or_assign(K&&, V&&); -}; - - -template -concept default_back_emplaceable = requires(T c) { - std::forward(c).emplace_back(); -}; - -template -concept back_emplaceable = - requires(T c, std::ranges::range_value_t> v) { - std::forward(c).emplace_back(std::move(v)); - } && - ( - !std::default_initializable>> || - default_back_emplaceable - ); - -template -concept default_emplaceable = requires(T c) { - std::forward(c).emplace(); + ~mapping_range() = delete; }; -template -concept emplaceable = - requires(T c, std::ranges::range_value_t> v) { - std::forward(c).emplace(std::move(v)); - } && - ( - !std::default_initializable>> || - default_emplaceable - ); - -template -concept growable_array_writable = back_emplaceable || emplaceable; - -template -struct dummy_growable_array -{ - T const* begin() const; - T const* end() const; - void emplace_back(T&&); - void emplace_back() requires std::default_initializable; -}; - -template -concept fixed_array_writable = - !growable_array_writable && - std::ranges::forward_range && - std::ranges::sized_range && - std::is_lvalue_reference_v> && - !std::is_const_v>> && - std::is_move_assignable_v>>; - -template -using dummy_fixed_array = T[1]; - -} // iris::ranges +} // iris::ranges::dummy #endif diff --git a/include/iris/requirements.hpp b/include/iris/requirements.hpp index 6a43bda..c120633 100644 --- a/include/iris/requirements.hpp +++ b/include/iris/requirements.hpp @@ -28,6 +28,12 @@ concept boolean_testable = detail::boolean_testable_impl && requires(T&& t) { { !std::forward(t) } -> detail::boolean_testable_impl; }; +template +concept half_equality_comparable = requires(std::remove_reference_t const& a, std::remove_reference_t const& b) { + { a == b } -> boolean_testable; + { a != b } -> boolean_testable; +}; + // https://eel.is/c++draft/utility.arg.requirements#tab:cpp17.equalitycomparable template concept Cpp17EqualityComparable = diff --git a/include/iris/run_length_sequence.hpp b/include/iris/run_length_sequence.hpp new file mode 100644 index 0000000..46f2965 --- /dev/null +++ b/include/iris/run_length_sequence.hpp @@ -0,0 +1,680 @@ +#ifndef IRIS_ZZ_RUN_LENGTH_SEQUENCE_HPP +#define IRIS_ZZ_RUN_LENGTH_SEQUENCE_HPP + +// SPDX-License-Identifier: MIT + +#include // IWYU pragma: keep +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include // IWYU pragma: keep +#include + +namespace iris::detail { +struct run_length_sequence_comp; +} // iris::detail + +namespace iris { + +template +struct run_length_run_ref +{ + T const& value; + interval span; +}; + +// A compressed bidirectional container that holds only one instance of `T` +// per each adjacent equivalent elements. +// +// Invariant: adjacent runs never compare equal. This makes the representation +// canonical, which `operator==` relies on. +// +// Iterator invalidation does not follow `RunContainerT`: any insertion that starts a +// new run may invalidate all iterators, regardless of `RunContainerT`. Reference +// stability of the elements follows `RunContainerT`. +// +// Precondition for heterogeneous comparison: `e == value` must hold iff +// `e == T(value)`; otherwise the run structure would depend on which overload +// of `emplace_back` (or relevant insertion members) was chosen. +// +// When inserting an element, `run_length_sequence` does lazy construction of the +// actual element object iff heterogeneous comparison is supported for the given +// type `U`; otherwise, +// - If `RunContainerT` supports `runs_.pop_back()` or `runs_.erase(iterator to the +// last inserted element)`, the element is first constructed in-place and +// the resulting object is used for comparison; when the comparison holds, the +// last element is popped back or erased. Otherwise, +// +// - The element is first constructed as a local variable `temp` and is used for +// comparison; when the comparison does not hold, the object is move-inserted +// into `RunContainerT` as if by `runs_.emplace_back(std::forward(temp))`. +// +// Internal structure is maintained with two independent containers, where the first +// container holds one element instance per each run and the other one holds the +// *offsets* that represent each insertion of elements. +// +// For example, when items are emplaced in this order: +// A A A B B C C C C +// +// then the internal state is: +// runs_: +// [A] [B] [C] +// +// offsets_: +// [0] [3] [5] [9 /* sentinel */] +template< + class T, + class IndexT = unsigned, + template class IndexedValuePairTT = indexed_value, + class RunContainerT = std::vector +> +class run_length_sequence +{ +public: + static_assert(!std::is_const_v); + static_assert(unsigned_numeric_integral && !std::is_const_v); + static_assert(sizeof(IndexT) >= sizeof(int), "Small index type has no practical benefit"); + static_assert(!std::is_const_v); + + using size_type = std::common_type_t; + using difference_type = std::ptrdiff_t; + + [[nodiscard]] static constexpr size_type max_size() noexcept + { + constexpr auto a = std::numeric_limits::max(); + constexpr auto b = std::numeric_limits::max(); + return std::cmp_less(b, a) ? static_cast(b) : static_cast(a); + } + +private: + using offsets_type = std::vector>; + + struct iterator_impl + { + private: + using value_iterator = std::ranges::iterator_t; + using offset_iterator = offsets_type::const_iterator; + + public: + using iterator_concept = std::bidirectional_iterator_tag; + using iterator_category = std::input_iterator_tag; // Our reference type is proxy, so this can only be input + + using value_type = IndexedValuePairTT< + IndexT, + std::iter_value_t + >; + using reference = IndexedValuePairTT< + IndexT, + std::iter_reference_t + >; + using difference_type = run_length_sequence::difference_type; + + constexpr iterator_impl() noexcept = default; + + constexpr explicit iterator_impl(value_iterator value_it, offset_iterator ofs_it) noexcept + : value_it_(std::move(value_it)) + , ofs_it_(std::move(ofs_it)) + {} + + constexpr explicit iterator_impl(value_iterator value_it, offset_iterator ofs_it, IndexT pos) noexcept + : value_it_(std::move(value_it)) + , ofs_it_(std::move(ofs_it)) + , rel_pos_(pos) + {} + + [[nodiscard]] constexpr reference operator*() const noexcept + { + return {index(), *value_it_}; + } + + constexpr iterator_impl& operator++() noexcept + { + auto ofs_next = std::next(ofs_it_); + if (++rel_pos_ == *ofs_next - *ofs_it_) { + ofs_it_ = std::move(ofs_next); + rel_pos_ = static_cast(0u); + ++value_it_; + } + return *this; + } + + [[nodiscard]] constexpr iterator_impl operator++(int) noexcept + { + auto temp{*this}; + ++*this; + return temp; + } + + constexpr iterator_impl& operator--() noexcept + { + if (rel_pos_ != static_cast(0u)) { + --rel_pos_; + return *this; + } + + auto const current_ofs = *ofs_it_--; + rel_pos_ = current_ofs - *ofs_it_ - static_cast(1u); + --value_it_; + return *this; + } + + [[nodiscard]] constexpr iterator_impl operator--(int) noexcept + { + auto temp{*this}; + --*this; + return temp; + } + + [[nodiscard]] constexpr bool operator==(iterator_impl const&) const noexcept = default; + + [[nodiscard]] friend constexpr difference_type operator-(iterator_impl const& a, iterator_impl const& b) noexcept + { + if (a == b) return 0; + return static_cast(a.index()) - static_cast(b.index()); + } + + private: + [[nodiscard]] constexpr IndexT index() const noexcept + { + return static_cast(*ofs_it_ + rel_pos_); + } + + value_iterator value_it_{}; + offset_iterator ofs_it_{}; + IndexT rel_pos_{}; + }; + +#ifdef NDEBUG +# define IRIS_ZZ_RUN_LENGTH_SEQUENCE_INVARIANT_GUARD +#else +# define IRIS_ZZ_RUN_LENGTH_SEQUENCE_INVARIANT_GUARD [[maybe_unused]] check_invariant_guard invariant_guard_{this}; +#endif + +public: + using item_type = T; + using index_type = IndexT; + using run_container_type = RunContainerT; + using allocator_type = run_container_type::allocator_type; + using run_ref = run_length_run_ref; + + using value_type = IndexedValuePairTT; + using const_reference = IndexedValuePairTT; + using reference = const_reference; + + using const_iterator = iterator_impl; + using iterator = const_iterator; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + + constexpr run_length_sequence() = default; + + constexpr explicit run_length_sequence(allocator_type const& alloc) noexcept + : runs_(alloc) + {} + + template R> + constexpr run_length_sequence(std::from_range_t, R&& r, allocator_type const& alloc = allocator_type()) + : runs_(alloc) + { + for (auto&& elem : r) { + this->emplace_back(std::forward(elem)); + } + } + + template It, std::sentinel_for Se> + constexpr run_length_sequence(It it, Se se, allocator_type const& alloc = allocator_type()) + : runs_(alloc) + { + for (; it != se; ++it) { + this->emplace_back(*it); + } + } + + constexpr run_length_sequence(std::initializer_list il, allocator_type const& alloc = allocator_type()) + : run_length_sequence(il.begin(), il.end(), alloc) + {} + + [[nodiscard]] constexpr const_iterator begin() const noexcept { check_range_concepts(); return const_iterator{std::ranges::begin(runs_), offsets_.begin()}; } + [[nodiscard]] constexpr const_iterator cbegin() const noexcept { return begin(); } + + [[nodiscard]] constexpr const_iterator end() const noexcept + { + check_range_concepts(); + if (offsets_.empty()) { + assert(std::ranges::empty(runs_)); + return const_iterator{std::ranges::end(runs_), offsets_.end()}; + } else { + assert(offsets_.size() >= 2); + return const_iterator{std::ranges::end(runs_), std::prev(offsets_.end())}; + } + } + [[nodiscard]] constexpr const_iterator cend() const noexcept { return end(); } + + [[nodiscard]] constexpr const_reverse_iterator rbegin() const noexcept { return const_reverse_iterator{end()}; } + [[nodiscard]] constexpr const_reverse_iterator crbegin() const noexcept { return rbegin(); } + [[nodiscard]] constexpr const_reverse_iterator rend() const noexcept { return const_reverse_iterator{begin()}; } + [[nodiscard]] constexpr const_reverse_iterator crend() const noexcept { return rend(); } + + [[nodiscard]] constexpr bool empty() const noexcept + { + static_assert(std::ranges::sized_range); + assert(std::ranges::empty(runs_) == offsets_.empty()); + return offsets_.empty(); + } + + [[nodiscard]] constexpr size_type size() const noexcept + { + static_assert(std::ranges::sized_range); + if (offsets_.empty()) return 0uz; + assert(!std::ranges::empty(runs_)); + assert(offsets_.size() >= 2); + return static_cast(offsets_.back()); + } + + constexpr void clear() noexcept + requires requires(RunContainerT& runs) { runs.clear(); } + { + static_assert(req::Cpp17Destructible); + IRIS_ZZ_RUN_LENGTH_SEQUENCE_INVARIANT_GUARD + runs_.clear(); + offsets_.clear(); + } + + [[nodiscard]] constexpr const_reference front() const noexcept IRIS_LIFETIMEBOUND + { + assert(!this->empty()); + return {static_cast(0u), container::front(runs_)}; + } + [[nodiscard]] constexpr const_reference back() const noexcept IRIS_LIFETIMEBOUND + { + assert(!this->empty()); + return {static_cast(offsets_.back() - static_cast(1u)), container::back(runs_)}; + } + + template + requires std::constructible_from && req::half_equality_comparable + constexpr const_reference emplace_back(U&& value) IRIS_LIFETIMEBOUND + { + check_range_concepts(); + static_assert(std::equality_comparable); + IRIS_ZZ_RUN_LENGTH_SEQUENCE_INVARIANT_GUARD + + if (offsets_.empty()) { + return this->emplace_back_on_empty(std::forward(value)); + + } else { + assert(!std::ranges::empty(runs_)); + assert(offsets_.size() >= 2); + if (offsets_.back() == max_size()) { + throwf("run_length_sequence capacity exceeded"); + } + + if (container::back(std::as_const(runs_)) == std::as_const(value)) { + // Equivalent element already exists; no need to insert. + return {offsets_.back()++, container::back(runs_)}; + } + // Need to insert new element + auto const new_pos = offsets_.back(); + offsets_.emplace_back(new_pos + static_cast(1u)); // new sentinel + [[maybe_unused]] ofs_insertion_guard ofs_insertion_guard{this}; + auto& elem = container::append_return(runs_, std::forward(value)); + ofs_insertion_guard.clear(); + return {new_pos, elem}; + } + } + + template + requires std::constructible_from + constexpr const_reference emplace_back(Args&&... args) IRIS_LIFETIMEBOUND + { + check_range_concepts(); + static_assert(std::equality_comparable); + IRIS_ZZ_RUN_LENGTH_SEQUENCE_INVARIANT_GUARD + + if (offsets_.empty()) { + return this->emplace_back_on_empty(std::forward(args)...); + + } else { + assert(!std::ranges::empty(runs_)); + assert(offsets_.size() >= 2); + if (offsets_.back() == max_size()) { + throwf("run_length_sequence capacity exceeded"); + } + if constexpr (requires { container::erase_back(runs_); }) { + auto& elem = container::append_return(runs_, std::forward(args)...); + [[maybe_unused]] elem_insertion_guard elem_insertion_guard{this}; + + auto const prev_it = std::ranges::prev(std::ranges::end(runs_), 2); + + if (std::as_const(*prev_it) == std::as_const(elem)) { + // Equivalent element already exists; no need to insert. + elem_insertion_guard.clear(); + container::erase_back(runs_); + return {offsets_.back()++, container::back(runs_)}; + } + + // Need to insert new element + auto const new_pos = offsets_.back(); + offsets_.emplace_back(new_pos + static_cast(1u)); // new sentinel + elem_insertion_guard.clear(); + return {new_pos, elem}; + + } else { + T temp(std::forward(args)...); + if (container::back(std::as_const(runs_)) == std::as_const(temp)) { + // Equivalent element already exists; no need to insert. + return {offsets_.back()++, container::back(runs_)}; + } + // Need to insert new element + auto const new_pos = offsets_.back(); + offsets_.emplace_back(new_pos + static_cast(1u)); // new sentinel + [[maybe_unused]] ofs_insertion_guard ofs_insertion_guard{this}; + auto& elem = container::append_return(runs_, std::move(temp)); + ofs_insertion_guard.clear(); + return {new_pos, elem}; + } + } + } + + // Offers heterogeneous comparison benefit + template + requires + (!std::same_as, T>) && + std::constructible_from + constexpr const_reference push_back(U&& value_like) IRIS_LIFETIMEBOUND + { + return this->emplace_back(std::forward(value_like)); + } + constexpr const_reference push_back(T const& value) IRIS_LIFETIMEBOUND + { + return this->emplace_back(value); + } + constexpr const_reference push_back(T&& value) IRIS_LIFETIMEBOUND + { + return this->emplace_back(std::move(value)); + } + + constexpr void pop_back() + noexcept(noexcept(container::erase_back(runs_))) + requires requires(RunContainerT& runs) { container::erase_back(runs); } + { + static_assert(req::Cpp17Destructible); + IRIS_ZZ_RUN_LENGTH_SEQUENCE_INVARIANT_GUARD + assert(!this->empty()); + assert(offsets_.size() >= 2); + + auto const n = offsets_.size(); + if (offsets_[n - 1] - offsets_[n - 2] == static_cast(1u)) { + container::erase_back(runs_); + offsets_.pop_back(); + if (offsets_.size() == 1) { + offsets_.clear(); // remove sentinel + } + + } else { + --offsets_.back(); + } + } + + constexpr void shrink_to_fit() + { + if constexpr (container::has_shrink_to_fit) { + runs_.shrink_to_fit(); + } + offsets_.shrink_to_fit(); + } + + template R> + constexpr void append_range(R&& r) + { + for (auto it = std::ranges::begin(r); it != std::ranges::end(r); ++it) { + this->emplace_back(*it); + } + } + + // --------------------------------------------------------------------- + + [[nodiscard]] constexpr RunContainerT const& runs() const noexcept + { + return runs_; + } + + [[nodiscard]] constexpr size_type run_count() const noexcept + { + static_assert(std::ranges::sized_range); + return static_cast(std::ranges::size(runs_)); + } + + [[nodiscard]] constexpr auto run_view() const noexcept + { + check_range_concepts(); + return std::views::zip_transform( + [](T const& value, auto const& bounds) noexcept -> run_ref { + return {value, interval{std::get<0>(bounds), std::get<1>(bounds)}}; + }, + runs_, offsets_ | std::views::pairwise + ); + } + + // --------------------------------------------------------------------- + + // Returns an iterator to the element at logical position `pos`, or `end()` + // when `pos >= size()`. + // + // Complexity: O(log run_count()) for locating the run, plus the cost of + // advancing an iterator of `RunContainerT` by the run index (O(1) when + // `RunContainerT` is random access). + [[nodiscard]] constexpr const_iterator nth(IndexT pos) const noexcept + { + check_range_concepts(); + if (pos >= this->size()) return this->end(); + + auto const ofs_next = std::ranges::upper_bound(offsets_, pos); + assert(ofs_next != offsets_.begin()); + assert(ofs_next != offsets_.end()); + auto const ofs_it = std::ranges::prev(ofs_next); + + auto const run_index = ofs_it - offsets_.begin(); + auto const value_it = std::ranges::next(std::ranges::begin(runs_), run_index); + + return const_iterator{value_it, ofs_it, static_cast(pos - *ofs_it)}; + } + + // --------------------------------------------------------------------- + + constexpr void swap(run_length_sequence& other) + noexcept(std::is_nothrow_swappable_v && std::is_nothrow_swappable_v) + { + using std::swap; + swap(runs_, other.runs_); + swap(offsets_, other.offsets_); + } + +private: + static constexpr void check_range_concepts() noexcept + { + static_assert(std::ranges::bidirectional_range); + static_assert(std::same_as, T>); + static_assert(std::same_as, T&>); + static_assert(std::same_as, T const&>); + + static_assert(container::back_pushable); + static_assert(container::back_accessible); + static_assert(container::front_accessible); + + static_assert(requires (RunContainerT& runs) { + { container::back(runs) } -> std::same_as; + { container::front(runs) } -> std::same_as; + }); + } + + template + constexpr const_reference emplace_back_on_empty(Args&&... args) IRIS_LIFETIMEBOUND + { + assert(this->empty()); + [[maybe_unused]] ofs_insertion_guard ofs_insertion_guard{this}; + offsets_.emplace_back(static_cast(0u)); + offsets_.emplace_back(static_cast(1u)); // sentinel + auto& elem = container::append_return(runs_, std::forward(args)...); + ofs_insertion_guard.clear(); + return {static_cast(0u), elem}; + } + + template + struct [[nodiscard]] ofs_insertion_guard + { + constexpr explicit ofs_insertion_guard(run_length_sequence* self) noexcept + : self_(self) + {} + + constexpr void clear() noexcept + { + self_ = nullptr; + } + + constexpr ~ofs_insertion_guard() noexcept + { + if (!self_) return; + if constexpr (WasEmpty) { + self_->offsets_.clear(); + } else { + self_->offsets_.pop_back(); + } + } + + private: + run_length_sequence* self_; + }; + template + friend struct ofs_insertion_guard; + + struct [[nodiscard]] elem_insertion_guard + { + constexpr explicit elem_insertion_guard(run_length_sequence* self) noexcept + : self_(self) + {} + + constexpr void clear() noexcept + { + self_ = nullptr; + } + + constexpr ~elem_insertion_guard() noexcept + { + if (!self_) return; + container::erase_back(self_->runs_); + } + + private: + run_length_sequence* self_; + }; + friend struct elem_insertion_guard; + +#ifndef NDEBUG + struct [[nodiscard]] check_invariant_guard + { + constexpr explicit check_invariant_guard(run_length_sequence const* self) noexcept + : self_(self) + {} + + constexpr ~check_invariant_guard() noexcept + { + auto const& offsets = self_->offsets_; + auto const& runs = self_->runs_; + if (offsets.empty()) { + assert(std::ranges::empty(runs)); + return; + } + assert(offsets.size() >= 2); + assert(offsets.size() == std::ranges::size(runs) + 1); + assert(offsets.front() == static_cast(0u)); + auto const n = offsets.size(); + assert(offsets[n - 2] < offsets[n - 1]); + + // Adjacent runs must differ + if (n >= 3) { + auto const last = std::ranges::prev(std::ranges::end(runs)); + auto const before_last = std::ranges::prev(last); + assert(!(*before_last == *last)); + } + } + + private: + run_length_sequence const* self_; + }; + friend struct check_invariant_guard; +#endif + + friend struct detail::run_length_sequence_comp; + + RunContainerT runs_; + offsets_type offsets_; + +#undef IRIS_ZZ_RUN_LENGTH_SEQUENCE_INVARIANT_GUARD +}; + +template class IndexedValuePairTT, class RunContainerT> +constexpr void swap( + run_length_sequence& a, + run_length_sequence& b +) + noexcept(noexcept(a.swap(b))) +{ + a.swap(b); +} + + +namespace detail { + +struct run_length_sequence_comp +{ + template class IndexedValuePairTT, class RunContainerT> + [[nodiscard]] static constexpr bool + equals( + run_length_sequence const& a, + run_length_sequence const& b + ) + noexcept(noexcept(std::declval() == std::declval())) + { + static_assert(std::equality_comparable); + static_assert(std::equality_comparable); + // Adjacent runs never compare equal (class invariant), so the representation + // is canonical and representational equality is logical equality. + return a.offsets_ == b.offsets_ && a.runs_ == b.runs_; + } +}; + +} // detail + +template class IndexedValuePairTT, class RunContainerT> +[[nodiscard]] constexpr bool +operator==( + run_length_sequence const& a, + run_length_sequence const& b +) + noexcept(noexcept(detail::run_length_sequence_comp::equals(a, b))) +{ + return detail::run_length_sequence_comp::equals(a, b); +} + +} // iris + +#endif diff --git a/include/iris/type_traits.hpp b/include/iris/type_traits.hpp index 06b93cb..6f0a51f 100644 --- a/include/iris/type_traits.hpp +++ b/include/iris/type_traits.hpp @@ -18,6 +18,25 @@ namespace iris { +template +concept signed_numeric_integral = + std::signed_integral && + !std::same_as, char> && + !std::same_as, wchar_t>; + +template +concept unsigned_numeric_integral = + std::unsigned_integral && + !std::same_as, bool> && + !std::same_as, char> && + !std::same_as, wchar_t> && + !std::same_as, char8_t> && + !std::same_as, char16_t> && + !std::same_as, char32_t>; + +template +concept numeric_integral = signed_numeric_integral || unsigned_numeric_integral; + template struct remove_cv { diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 6d72e40..ead5c33 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -183,6 +183,7 @@ if(PROJECT_IS_TOP_LEVEL) core type_traits ranges + container_traits stdint enum indirect @@ -196,6 +197,8 @@ if(PROJECT_IS_TOP_LEVEL) alloy alloy_visit marshal + indexed_value + run_length_sequence ) foreach(test_name IN LISTS IRIS_TEST_IRIS_TESTS) iris_define_internal_test(${test_name} ${test_name}.cpp) diff --git a/test/container_traits.cpp b/test/container_traits.cpp new file mode 100644 index 0000000..4fdfb88 --- /dev/null +++ b/test/container_traits.cpp @@ -0,0 +1,800 @@ +// SPDX-License-Identifier: MIT + +#include "iris_test.hpp" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include // TODO +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std::string_view_literals; +using namespace std::string_literals; + +TEST_CASE("container: map") +{ + STATIC_CHECK(iris::ranges::key_value_range>); + STATIC_CHECK(iris::ranges::mapping_range>); + STATIC_CHECK(iris::container::mapping_container>); + STATIC_CHECK(iris::container::unique_mapping_container>); + + STATIC_CHECK(iris::ranges::key_value_range>); + STATIC_CHECK(iris::ranges::mapping_range>); + STATIC_CHECK(iris::container::mapping_container>); + STATIC_CHECK(!iris::container::unique_mapping_container>); + + STATIC_CHECK(iris::ranges::key_value_range>); + STATIC_CHECK(iris::ranges::mapping_range>); + STATIC_CHECK(iris::container::mapping_container>); + STATIC_CHECK(iris::container::unique_mapping_container>); + + STATIC_CHECK(iris::ranges::key_value_range>); + STATIC_CHECK(iris::ranges::mapping_range>); + STATIC_CHECK(iris::container::mapping_container>); + STATIC_CHECK(!iris::container::unique_mapping_container>); + + STATIC_CHECK(iris::ranges::key_value_range>); + STATIC_CHECK(iris::ranges::mapping_range>); + STATIC_CHECK(iris::container::mapping_container>); + STATIC_CHECK(iris::container::unique_mapping_container>); + + STATIC_CHECK(iris::ranges::key_value_range>); + STATIC_CHECK(iris::ranges::mapping_range>); + STATIC_CHECK(iris::container::mapping_container>); + STATIC_CHECK(!iris::container::unique_mapping_container>); + + STATIC_CHECK(iris::ranges::key_value_range>>); + STATIC_CHECK(!iris::ranges::mapping_range>>); + STATIC_CHECK(!iris::container::mapping_container>>); + + STATIC_CHECK(iris::ranges::key_value_range>>); + STATIC_CHECK(!iris::ranges::mapping_range>>); + STATIC_CHECK(!iris::container::mapping_container>>); +} + +TEST_CASE("container: compatible range/iterator") +{ + static_assert(!std::is_constructible_v, std::from_range_t, std::vector>); + STATIC_CHECK(iris::container::compatible_iterator::const_iterator, std::string>); + + static_assert(std::is_constructible_v, std::vector::const_iterator, std::vector::const_iterator>); + STATIC_CHECK(!iris::container::compatible_range, std::string>); +} + +TEST_CASE("container: member traits") +{ + STATIC_CHECK(iris::container::back_pushable>); + STATIC_CHECK(iris::container::back_pushable&>); + STATIC_CHECK(!iris::container::back_pushable const&>); + STATIC_CHECK(iris::container::growable_array>); + STATIC_CHECK(iris::container::growable_array&>); + STATIC_CHECK(!iris::container::growable_array const&>); + STATIC_CHECK(!iris::container::fixed_array>); + + STATIC_CHECK(iris::container::appendable>); + STATIC_CHECK(iris::container::appendable&>); + STATIC_CHECK(!iris::container::appendable const&>); + STATIC_CHECK(iris::container::growable_array>); + STATIC_CHECK(iris::container::growable_array&>); + STATIC_CHECK(!iris::container::growable_array const&>); + STATIC_CHECK(!iris::container::fixed_array>); + + STATIC_CHECK(iris::container::fixed_array); + STATIC_CHECK(iris::container::fixed_array); + STATIC_CHECK(!iris::container::fixed_array); + STATIC_CHECK(!iris::container::growable_array); + + STATIC_CHECK(iris::container::fixed_array>); + STATIC_CHECK(iris::container::fixed_array&>); + STATIC_CHECK(!iris::container::fixed_array const&>); + STATIC_CHECK(!iris::container::growable_array>); + + STATIC_CHECK(iris::container::fixed_array>); + STATIC_CHECK(iris::container::fixed_array&>); + STATIC_CHECK(iris::container::fixed_array const&>); + STATIC_CHECK(!iris::container::growable_array>); + + STATIC_CHECK(!iris::container::fixed_array>); + STATIC_CHECK(!iris::container::fixed_array&>); + STATIC_CHECK(!iris::container::fixed_array const&>); + STATIC_CHECK(!iris::container::growable_array>); + + STATIC_CHECK(iris::container::fixed_array>); + STATIC_CHECK(iris::container::fixed_array&>); + STATIC_CHECK(iris::container::fixed_array const&>); + STATIC_CHECK(!iris::container::growable_array>); +} + +TEST_CASE("container: dummy types") +{ + STATIC_CHECK(iris::ranges::key_value_range>); + STATIC_CHECK(!iris::ranges::mapping_range>); + + STATIC_CHECK(iris::ranges::key_value_range>); + STATIC_CHECK(iris::ranges::mapping_range>); + STATIC_CHECK(!iris::container::mapping_container>); + + STATIC_CHECK(iris::ranges::key_value_range>); + STATIC_CHECK(iris::ranges::mapping_range>); + STATIC_CHECK(iris::container::mapping_container>); + STATIC_CHECK(!iris::container::unique_mapping_container>); + + STATIC_CHECK(iris::ranges::key_value_range>); + STATIC_CHECK(iris::ranges::mapping_range>); + STATIC_CHECK(iris::container::mapping_container>); + STATIC_CHECK(iris::container::unique_mapping_container>); + + STATIC_CHECK(iris::container::growable_array>); + STATIC_CHECK(iris::container::fixed_array>); +} + + +struct recording_item +{ + [[nodiscard]] static recording_item make_moved(recording_item const& other) + { + return recording_item{"move[" + other.log + "]"}; + } + + recording_item() + { + log += "default"; + } + + recording_item(int a) + { + log += std::format("{}", a); + } + + recording_item(int a, int b) + { + log += std::format("({},{})", a, b); + } + + recording_item(recording_item const& other) + { + log = "copy[" + other.log + "]"; + } + + recording_item(recording_item&& other) // NOLINT(cppcoreguidelines-noexcept-move-operations, performance-noexcept-move-constructor) + { + log = "move[" + other.log + "]"; + } + + recording_item& operator=(recording_item const&) = delete; + recording_item& operator=(recording_item&&) = default; + + [[nodiscard]] bool operator==(recording_item const&) const = default; + + std::string log; + +private: + explicit recording_item(std::string override_log) + : log(std::move(override_log)) + {} +}; + +template<> +struct std::formatter : std::formatter +{ + auto format(recording_item const& rec, auto& ctx) const + { + return std::formatter::format(rec.log, ctx); + } +}; + +template +struct recording_container +{ + recording_container() + { + elems.reserve(100); + } + + auto begin() const + { + if constexpr (IsFrontTest) { + return std::reverse_iterator{elems.end()}; + } else { + return elems.begin(); + } + } + auto begin() + { + if constexpr (IsFrontTest) { + return std::reverse_iterator{elems.end()}; + } else { + return elems.begin(); + } + } + auto end() const + { + if constexpr (IsFrontTest) { + return std::reverse_iterator{elems.begin()}; + } else { + return elems.end(); + } + } + auto end() + { + if constexpr (IsFrontTest) { + return std::reverse_iterator{elems.begin()}; + } else { + return elems.end(); + } + } + + template + decltype(auto) emplace_front(Args&&... args) requires IsFrontTest && HasEmplaceMeow + { + log += " emplace_front"; + return elems.emplace_back(std::forward(args)...); + } + + void push_front(T const& value) requires IsFrontTest && HasPushMeow + { + log += " push_front"; + elems.push_back(value); + } + + void push_front(T&& value) requires IsFrontTest && HasPushMeow + { + log += " push_front"; + elems.push_back(std::move(value)); + } + + template + decltype(auto) emplace_back(Args&&... args) requires (!IsFrontTest) && HasEmplaceMeow + { + log += " emplace_back"; + return elems.emplace_back(std::forward(args)...); + } + + void push_back(T const& value) requires (!IsFrontTest) && HasPushMeow + { + log += " push_back"; + elems.push_back(value); + } + + void push_back(T&& value) requires (!IsFrontTest) && HasPushMeow + { + log += " push_back"; + elems.push_back(std::move(value)); + } + + template + auto emplace(auto it, Args&&... args) requires HasEmplace + { + log += " emplace"; + if constexpr (IsFrontTest) { + return elems.emplace(it.base(), std::forward(args)...); + } else { + return elems.emplace(it, std::forward(args)...); + } + } + + auto insert(auto it, T const& value) requires HasInsert + { + log += " insert"; + if constexpr (IsFrontTest) { + return elems.insert(it.base(), value); + } else { + return elems.insert(it, value); + } + } + + auto insert(auto it, T&& value) requires HasInsert + { + log += " insert"; + if constexpr (IsFrontTest) { + return elems.insert(it.base(), std::move(value)); + } else { + return elems.insert(it, std::move(value)); + } + } + + [[nodiscard]] std::string elems_str() const + { + return elems | std::views::transform([](T const& value) { + return std::format("{}", value); + }) | std::views::join_with("|"sv) | std::ranges::to(); + } + + std::vector elems; + std::string log; +}; + +static_assert(std::ranges::range>); +static_assert(std::same_as>, int&>); +static_assert(std::same_as const>, int const&>); + +TEST_CASE("container: prepend") +{ + { + // emplace_front + push_front + emplace + insert + using Cont = recording_container; + + STATIC_CHECK(iris::container::front_pushable); + STATIC_CHECK(iris::container::front_pushable); + STATIC_CHECK(iris::container::front_pushable); + + STATIC_CHECK(iris::container::default_prependable); + STATIC_CHECK(iris::container::prependable); + STATIC_CHECK(iris::container::prependable); + STATIC_CHECK(iris::container::prependable); + + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + + Cont cont; + std::string elems_str_expected; + std::string cont_log_expected; + + iris::container::prepend(cont); cont_log_expected += " emplace_front"; elems_str_expected += "default"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + + iris::container::prepend(cont, 1); cont_log_expected += " emplace_front"; elems_str_expected += "|1"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + + iris::container::prepend(cont, 2, 3); cont_log_expected += " emplace_front"; elems_str_expected += "|(2,3)"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + + { + auto&& elem = iris::container::prepend_return(cont); cont_log_expected += " emplace_front"; elems_str_expected += "|default"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + CHECK(elem == recording_item{}); + } + { + auto&& elem = iris::container::prepend_return(cont, 4); cont_log_expected += " emplace_front"; elems_str_expected += "|4"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + CHECK(elem == recording_item{4}); + } + { + auto&& elem = iris::container::prepend_return(cont, 5, 6); cont_log_expected += " emplace_front"; elems_str_expected += "|(5,6)"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + CHECK(elem == recording_item{5, 6}); + } + } + { + // push_front + emplace + insert + using Cont = recording_container; + + STATIC_CHECK(!iris::container::front_pushable); + STATIC_CHECK(iris::container::front_pushable); + STATIC_CHECK(!iris::container::front_pushable); + + STATIC_CHECK(iris::container::default_prependable); + STATIC_CHECK(iris::container::prependable); + STATIC_CHECK(iris::container::prependable); + STATIC_CHECK(iris::container::prependable); + + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + + Cont cont; + std::string elems_str_expected; + std::string cont_log_expected; + + iris::container::prepend(cont); cont_log_expected += " emplace"; elems_str_expected += "default"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + + iris::container::prepend(cont, 1); cont_log_expected += " push_front"; elems_str_expected += "|move[1]"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + + iris::container::prepend(cont, 2, 3); cont_log_expected += " emplace"; elems_str_expected += "|(2,3)"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + + { + auto&& elem = iris::container::prepend_return(cont); cont_log_expected += " emplace"; elems_str_expected += "|default"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + CHECK(elem == recording_item{}); + } + { + auto&& elem = iris::container::prepend_return(cont, 4); cont_log_expected += " push_front"; elems_str_expected += "|move[4]"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + CHECK(elem == recording_item::make_moved(recording_item{4})); + } + { + auto&& elem = iris::container::prepend_return(cont, 5, 6); cont_log_expected += " emplace"; elems_str_expected += "|(5,6)"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + CHECK(elem == recording_item{5, 6}); + } + } + { + // emplace + insert + using Cont = recording_container; + + STATIC_CHECK(!iris::container::front_pushable); + STATIC_CHECK(!iris::container::front_pushable); + STATIC_CHECK(!iris::container::front_pushable); + + STATIC_CHECK(iris::container::default_prependable); + STATIC_CHECK(iris::container::prependable); + STATIC_CHECK(iris::container::prependable); + STATIC_CHECK(iris::container::prependable); + + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + + Cont cont; + std::string elems_str_expected; + std::string cont_log_expected; + + iris::container::prepend(cont); cont_log_expected += " emplace"; elems_str_expected += "default"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + + iris::container::prepend(cont, 1); cont_log_expected += " emplace"; elems_str_expected += "|1"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + + iris::container::prepend(cont, 2, 3); cont_log_expected += " emplace"; elems_str_expected += "|(2,3)"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + + { + auto&& elem = iris::container::prepend_return(cont); cont_log_expected += " emplace"; elems_str_expected += "|default"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + CHECK(elem == recording_item{}); + } + { + auto&& elem = iris::container::prepend_return(cont, 4); cont_log_expected += " emplace"; elems_str_expected += "|4"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + CHECK(elem == recording_item{4}); + } + { + auto&& elem = iris::container::prepend_return(cont, 5, 6); cont_log_expected += " emplace"; elems_str_expected += "|(5,6)"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + CHECK(elem == recording_item{5, 6}); + } + } + { + // insert + using Cont = recording_container; + + STATIC_CHECK(!iris::container::front_pushable); + STATIC_CHECK(!iris::container::front_pushable); + STATIC_CHECK(!iris::container::front_pushable); + + STATIC_CHECK(iris::container::default_prependable); + STATIC_CHECK(iris::container::prependable); + STATIC_CHECK(iris::container::prependable); + STATIC_CHECK(!iris::container::prependable); + + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(!std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(!std::invocable); + + Cont cont; + std::string elems_str_expected; + std::string cont_log_expected; + + iris::container::prepend(cont); cont_log_expected += " insert"; elems_str_expected += "move[default]"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + + iris::container::prepend(cont, 1); cont_log_expected += " insert"; elems_str_expected += "|move[1]"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + + { + auto&& elem = iris::container::prepend_return(cont); cont_log_expected += " insert"; elems_str_expected += "|move[default]"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + CHECK(elem == recording_item::make_moved(recording_item{})); + } + { + auto&& elem = iris::container::prepend_return(cont, 4); cont_log_expected += " insert"; elems_str_expected += "|move[4]"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + CHECK(elem == recording_item::make_moved(recording_item{4})); + } + } + { + // no accessor + using Cont = recording_container; + + STATIC_CHECK(!iris::container::front_pushable); + STATIC_CHECK(!iris::container::front_pushable); + STATIC_CHECK(!iris::container::front_pushable); + + STATIC_CHECK(!iris::container::default_prependable); + STATIC_CHECK(!iris::container::prependable); + STATIC_CHECK(!iris::container::prependable); + STATIC_CHECK(!iris::container::prependable); + + STATIC_CHECK(!std::invocable); + STATIC_CHECK(!std::invocable); + STATIC_CHECK(!std::invocable); + STATIC_CHECK(!std::invocable); + STATIC_CHECK(!std::invocable); + STATIC_CHECK(!std::invocable); + } +} + +TEST_CASE("container: append") +{ + { + // emplace_back + push_back + emplace + insert + using Cont = recording_container; + + STATIC_CHECK(iris::container::back_pushable); + STATIC_CHECK(iris::container::back_pushable); + STATIC_CHECK(iris::container::back_pushable); + + STATIC_CHECK(iris::container::default_appendable); + STATIC_CHECK(iris::container::appendable); + STATIC_CHECK(iris::container::appendable); + STATIC_CHECK(iris::container::appendable); + + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + + Cont cont; + std::string elems_str_expected; + std::string cont_log_expected; + + iris::container::append(cont); cont_log_expected += " emplace_back"; elems_str_expected += "default"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + + iris::container::append(cont, 1); cont_log_expected += " emplace_back"; elems_str_expected += "|1"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + + iris::container::append(cont, 2, 3); cont_log_expected += " emplace_back"; elems_str_expected += "|(2,3)"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + + { + auto&& elem = iris::container::append_return(cont); cont_log_expected += " emplace_back"; elems_str_expected += "|default"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + CHECK(elem == recording_item{}); + } + { + auto&& elem = iris::container::append_return(cont, 4); cont_log_expected += " emplace_back"; elems_str_expected += "|4"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + CHECK(elem == recording_item{4}); + } + { + auto&& elem = iris::container::append_return(cont, 5, 6); cont_log_expected += " emplace_back"; elems_str_expected += "|(5,6)"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + CHECK(elem == recording_item{5, 6}); + } + } + { + // push_back + emplace + insert + using Cont = recording_container; + + STATIC_CHECK(!iris::container::back_pushable); + STATIC_CHECK(iris::container::back_pushable); + STATIC_CHECK(!iris::container::back_pushable); + + STATIC_CHECK(iris::container::default_appendable); + STATIC_CHECK(iris::container::appendable); + STATIC_CHECK(iris::container::appendable); + STATIC_CHECK(iris::container::appendable); + + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + + Cont cont; + std::string elems_str_expected; + std::string cont_log_expected; + + iris::container::append(cont); cont_log_expected += " emplace"; elems_str_expected += "default"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + + iris::container::append(cont, 1); cont_log_expected += " push_back"; elems_str_expected += "|move[1]"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + + iris::container::append(cont, 2, 3); cont_log_expected += " emplace"; elems_str_expected += "|(2,3)"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + + { + auto&& elem = iris::container::append_return(cont); cont_log_expected += " emplace"; elems_str_expected += "|default"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + CHECK(elem == recording_item{}); + } + { + auto&& elem = iris::container::append_return(cont, 4); cont_log_expected += " push_back"; elems_str_expected += "|move[4]"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + CHECK(elem == recording_item::make_moved(recording_item{4})); + } + { + auto&& elem = iris::container::append_return(cont, 5, 6); cont_log_expected += " emplace"; elems_str_expected += "|(5,6)"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + CHECK(elem == recording_item{5, 6}); + } + } + { + // emplace + insert + using Cont = recording_container; + + STATIC_CHECK(!iris::container::back_pushable); + STATIC_CHECK(!iris::container::back_pushable); + STATIC_CHECK(!iris::container::back_pushable); + + STATIC_CHECK(iris::container::default_appendable); + STATIC_CHECK(iris::container::appendable); + STATIC_CHECK(iris::container::appendable); + STATIC_CHECK(iris::container::appendable); + + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + + Cont cont; + std::string elems_str_expected; + std::string cont_log_expected; + + iris::container::append(cont); cont_log_expected += " emplace"; elems_str_expected += "default"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + + iris::container::append(cont, 1); cont_log_expected += " emplace"; elems_str_expected += "|1"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + + iris::container::append(cont, 2, 3); cont_log_expected += " emplace"; elems_str_expected += "|(2,3)"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + + { + auto&& elem = iris::container::append_return(cont); cont_log_expected += " emplace"; elems_str_expected += "|default"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + CHECK(elem == recording_item{}); + } + { + auto&& elem = iris::container::append_return(cont, 4); cont_log_expected += " emplace"; elems_str_expected += "|4"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + CHECK(elem == recording_item{4}); + } + { + auto&& elem = iris::container::append_return(cont, 5, 6); cont_log_expected += " emplace"; elems_str_expected += "|(5,6)"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + CHECK(elem == recording_item{5, 6}); + } + } + { + // insert + using Cont = recording_container; + + STATIC_CHECK(!iris::container::back_pushable); + STATIC_CHECK(!iris::container::back_pushable); + STATIC_CHECK(!iris::container::back_pushable); + + STATIC_CHECK(iris::container::default_appendable); + STATIC_CHECK(iris::container::appendable); + STATIC_CHECK(iris::container::appendable); + STATIC_CHECK(!iris::container::appendable); + + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(!std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(std::invocable); + STATIC_CHECK(!std::invocable); + + Cont cont; + std::string elems_str_expected; + std::string cont_log_expected; + + iris::container::append(cont); cont_log_expected += " insert"; elems_str_expected += "move[default]"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + + iris::container::append(cont, 1); cont_log_expected += " insert"; elems_str_expected += "|move[1]"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + + { + auto&& elem = iris::container::append_return(cont); cont_log_expected += " insert"; elems_str_expected += "|move[default]"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + CHECK(elem == recording_item::make_moved(recording_item{})); + } + { + auto&& elem = iris::container::append_return(cont, 4); cont_log_expected += " insert"; elems_str_expected += "|move[4]"; + CHECK(cont.log == cont_log_expected); + REQUIRE(cont.elems_str() == elems_str_expected); + CHECK(elem == recording_item::make_moved(recording_item{4})); + } + } + { + // no accessor + using Cont = recording_container; + + STATIC_CHECK(!iris::container::back_pushable); + STATIC_CHECK(!iris::container::back_pushable); + STATIC_CHECK(!iris::container::back_pushable); + + STATIC_CHECK(!iris::container::default_appendable); + STATIC_CHECK(!iris::container::appendable); + STATIC_CHECK(!iris::container::appendable); + STATIC_CHECK(!iris::container::appendable); + + STATIC_CHECK(!std::invocable); + STATIC_CHECK(!std::invocable); + STATIC_CHECK(!std::invocable); + STATIC_CHECK(!std::invocable); + STATIC_CHECK(!std::invocable); + STATIC_CHECK(!std::invocable); + } +} diff --git a/test/indexed_value.cpp b/test/indexed_value.cpp new file mode 100644 index 0000000..412d41e --- /dev/null +++ b/test/indexed_value.cpp @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: MIT + +#include "iris_test.hpp" + +#include + +#include +#include +#include +#include + +using iris::indexed_value; + +TEST_CASE("indexed_value") +{ + using V = indexed_value; + //using CV = indexed_value; + using LR = indexed_value; + using CLR = indexed_value; + using RR = indexed_value; + using CRR = indexed_value; + + // --------------------------------------------------- + + STATIC_CHECK(std::is_aggregate_v); + STATIC_CHECK(std::is_aggregate_v); + + STATIC_CHECK(std::is_trivially_copyable_v>); + STATIC_CHECK(std::is_trivially_copyable_v>); + STATIC_CHECK(!std::is_trivially_copyable_v>); + + // --------------------------------------------------- + + STATIC_CHECK(std::same_as, V>); + STATIC_CHECK(std::same_as, V>); + STATIC_CHECK(std::same_as, V>); + STATIC_CHECK(std::same_as, V>); + + // --------------------------------------------------- + + STATIC_CHECK(std::same_as, std::common_reference_t>); + STATIC_CHECK(std::same_as, std::common_reference_t>); + STATIC_CHECK(std::same_as, std::common_reference_t>); + + STATIC_CHECK(std::common_reference_with); + STATIC_CHECK(std::common_reference_with); + STATIC_CHECK(std::common_reference_with); + STATIC_CHECK(std::same_as, indexed_value>); + STATIC_CHECK(std::same_as, indexed_value>); + STATIC_CHECK(std::same_as, indexed_value>); + + STATIC_CHECK(std::common_reference_with); + STATIC_CHECK(std::common_reference_with); + STATIC_CHECK(std::common_reference_with); + STATIC_CHECK(std::same_as, indexed_value>); + STATIC_CHECK(std::same_as, indexed_value>); + STATIC_CHECK(std::same_as, indexed_value>); + + // --------------------------------------------------- + + STATIC_CHECK(std::same_as< + std::common_type_t, indexed_value>, + indexed_value + >); + + // --------------------------------------------------- + + STATIC_CHECK(std::convertible_to>); + STATIC_CHECK(std::convertible_to>); + STATIC_CHECK(std::convertible_to>); + STATIC_CHECK(std::convertible_to>); + + // --------------------------------------------------- + + STATIC_CHECK(std::same_as(std::declval())), int&>); + STATIC_CHECK(std::same_as(std::declval())), int const&>); + STATIC_CHECK(std::same_as(std::declval())), int&&>); + + STATIC_CHECK(std::same_as(std::declval())), std::string&>); + STATIC_CHECK(std::same_as(std::declval())), std::string const&>); + STATIC_CHECK(std::same_as(std::declval())), std::string&&>); + + STATIC_CHECK(std::same_as(std::declval())), std::string&>); + STATIC_CHECK(std::same_as(std::declval())), std::string&&>); + STATIC_CHECK(std::same_as(std::declval())), std::string const&>); + + // --------------------------------------------------- + + STATIC_CHECK(std::tuple_size_v == 2); + STATIC_CHECK(std::same_as, int>); + STATIC_CHECK(std::same_as, std::string>); + + STATIC_CHECK(std::same_as, std::string&>); + STATIC_CHECK(std::same_as, std::string const&>); + + { + std::string value{"foo"}; + LR indexed{42, value}; + CHECK(indexed == indexed); + CHECK((indexed <=> indexed) == std::strong_ordering::equal); + auto [index, element] = indexed; + CHECK(index == 42); + CHECK(&element == &value); + } +} + +TEST_CASE("indexed_value: tuple/pair") +{ + STATIC_CHECK(std::equal_to{}(indexed_value{}, indexed_value{})); + + STATIC_CHECK(std::equal_to{}(indexed_value{}, std::tuple{})); + STATIC_CHECK(std::equal_to{}(std::tuple{}, indexed_value{})); + + STATIC_CHECK(std::equal_to{}(indexed_value{}, std::pair{})); + STATIC_CHECK(std::equal_to{}(std::pair{}, indexed_value{})); +} diff --git a/test/ranges.cpp b/test/ranges.cpp index 08d69ba..fda8194 100644 --- a/test/ranges.cpp +++ b/test/ranges.cpp @@ -2,16 +2,15 @@ #include "iris_test.hpp" +#include #include +#include +#include #include #include -#include -#include #include #include -#include -#include template struct FakeNonRangeMap @@ -64,48 +63,10 @@ struct FakeRangeMapWithNonTupleKey value_type const* end() const; }; -TEST_CASE("ranges: map related traits") +TEST_CASE("ranges: as_map") { using iris::ranges::as_map; - STATIC_CHECK(iris::ranges::key_value_range>); - STATIC_CHECK(iris::ranges::mapping_range>); - STATIC_CHECK(iris::ranges::mapping_container>); - STATIC_CHECK(iris::ranges::unique_mapping_container>); - - STATIC_CHECK(iris::ranges::key_value_range>); - STATIC_CHECK(iris::ranges::mapping_range>); - STATIC_CHECK(iris::ranges::mapping_container>); - STATIC_CHECK(!iris::ranges::unique_mapping_container>); - - STATIC_CHECK(iris::ranges::key_value_range>); - STATIC_CHECK(iris::ranges::mapping_range>); - STATIC_CHECK(iris::ranges::mapping_container>); - STATIC_CHECK(iris::ranges::unique_mapping_container>); - - STATIC_CHECK(iris::ranges::key_value_range>); - STATIC_CHECK(iris::ranges::mapping_range>); - STATIC_CHECK(iris::ranges::mapping_container>); - STATIC_CHECK(!iris::ranges::unique_mapping_container>); - - STATIC_CHECK(iris::ranges::key_value_range>); - STATIC_CHECK(iris::ranges::mapping_range>); - STATIC_CHECK(iris::ranges::mapping_container>); - STATIC_CHECK(iris::ranges::unique_mapping_container>); - - STATIC_CHECK(iris::ranges::key_value_range>); - STATIC_CHECK(iris::ranges::mapping_range>); - STATIC_CHECK(iris::ranges::mapping_container>); - STATIC_CHECK(!iris::ranges::unique_mapping_container>); - - STATIC_CHECK(iris::ranges::key_value_range>>); - STATIC_CHECK(!iris::ranges::mapping_range>>); - STATIC_CHECK(!iris::ranges::mapping_container>>); - - STATIC_CHECK(iris::ranges::key_value_range>>); - STATIC_CHECK(!iris::ranges::mapping_range>>); - STATIC_CHECK(!iris::ranges::mapping_container>>); - // --------------------------------------------------- // Viewed maps { @@ -117,19 +78,19 @@ TEST_CASE("ranges: map related traits") STATIC_CHECK(iris::ranges::key_value_range() | filter)>); STATIC_CHECK(!iris::ranges::mapping_range() | filter)>); STATIC_CHECK(iris::ranges::mapping_range() | filter | as_map)>); - STATIC_CHECK(!iris::ranges::mapping_container() | filter | as_map)>); + STATIC_CHECK(!iris::container::mapping_container() | filter | as_map)>); // Map& STATIC_CHECK(iris::ranges::key_value_range() | filter)>); STATIC_CHECK(!iris::ranges::mapping_range() | filter)>); STATIC_CHECK(iris::ranges::mapping_range() | filter | as_map)>); - STATIC_CHECK(!iris::ranges::mapping_container() | filter | as_map)>); + STATIC_CHECK(!iris::container::mapping_container() | filter | as_map)>); // Map const& STATIC_CHECK(iris::ranges::key_value_range() | filter)>); STATIC_CHECK(!iris::ranges::mapping_range() | filter)>); STATIC_CHECK(iris::ranges::mapping_range() | filter | as_map)>); - STATIC_CHECK(!iris::ranges::mapping_container() | filter | as_map)>); + STATIC_CHECK(!iris::container::mapping_container() | filter | as_map)>); } { using PairVec = std::vector>; @@ -141,7 +102,7 @@ TEST_CASE("ranges: map related traits") STATIC_CHECK(iris::ranges::key_value_range() | filter)>); STATIC_CHECK(!iris::ranges::mapping_range() | filter)>); STATIC_CHECK(iris::ranges::mapping_range() | filter | as_map)>); - STATIC_CHECK(!iris::ranges::mapping_container() | filter | as_map)>); + STATIC_CHECK(!iris::container::mapping_container() | filter | as_map)>); // Map& STATIC_CHECK(iris::ranges::key_value_range())>); @@ -150,7 +111,7 @@ TEST_CASE("ranges: map related traits") STATIC_CHECK(iris::ranges::key_value_range() | filter)>); STATIC_CHECK(!iris::ranges::mapping_range() | filter)>); STATIC_CHECK(iris::ranges::mapping_range() | filter | as_map)>); - STATIC_CHECK(!iris::ranges::mapping_container() | filter | as_map)>); + STATIC_CHECK(!iris::container::mapping_container() | filter | as_map)>); // Map const& STATIC_CHECK(iris::ranges::key_value_range())>); @@ -159,7 +120,7 @@ TEST_CASE("ranges: map related traits") STATIC_CHECK(iris::ranges::key_value_range() | filter)>); STATIC_CHECK(!iris::ranges::mapping_range() | filter)>); STATIC_CHECK(iris::ranges::mapping_range() | filter | as_map)>); - STATIC_CHECK(!iris::ranges::mapping_container() | filter | as_map)>); + STATIC_CHECK(!iris::container::mapping_container() | filter | as_map)>); } } @@ -184,69 +145,5 @@ TEST_CASE("ranges: map related traits") STATIC_CHECK(!iris::ranges::key_value_range>); } -TEST_CASE("ranges: container related traits") -{ - STATIC_CHECK(iris::ranges::back_emplaceable>); - STATIC_CHECK(iris::ranges::back_emplaceable&>); - STATIC_CHECK(!iris::ranges::back_emplaceable const&>); - STATIC_CHECK(iris::ranges::growable_array_writable>); - STATIC_CHECK(iris::ranges::growable_array_writable&>); - STATIC_CHECK(!iris::ranges::growable_array_writable const&>); - STATIC_CHECK(!iris::ranges::fixed_array_writable>); - - STATIC_CHECK(iris::ranges::emplaceable>); - STATIC_CHECK(iris::ranges::emplaceable&>); - STATIC_CHECK(!iris::ranges::emplaceable const&>); - STATIC_CHECK(iris::ranges::growable_array_writable>); - STATIC_CHECK(iris::ranges::growable_array_writable&>); - STATIC_CHECK(!iris::ranges::growable_array_writable const&>); - STATIC_CHECK(!iris::ranges::fixed_array_writable>); - - STATIC_CHECK(iris::ranges::fixed_array_writable); - STATIC_CHECK(iris::ranges::fixed_array_writable); - STATIC_CHECK(!iris::ranges::fixed_array_writable); - STATIC_CHECK(!iris::ranges::growable_array_writable); - - STATIC_CHECK(iris::ranges::fixed_array_writable>); - STATIC_CHECK(iris::ranges::fixed_array_writable&>); - STATIC_CHECK(!iris::ranges::fixed_array_writable const&>); - STATIC_CHECK(!iris::ranges::growable_array_writable>); - - STATIC_CHECK(iris::ranges::fixed_array_writable>); - STATIC_CHECK(iris::ranges::fixed_array_writable&>); - STATIC_CHECK(iris::ranges::fixed_array_writable const&>); - STATIC_CHECK(!iris::ranges::growable_array_writable>); - - STATIC_CHECK(!iris::ranges::fixed_array_writable>); - STATIC_CHECK(!iris::ranges::fixed_array_writable&>); - STATIC_CHECK(!iris::ranges::fixed_array_writable const&>); - STATIC_CHECK(!iris::ranges::growable_array_writable>); - - STATIC_CHECK(iris::ranges::fixed_array_writable>); - STATIC_CHECK(iris::ranges::fixed_array_writable&>); - STATIC_CHECK(iris::ranges::fixed_array_writable const&>); - STATIC_CHECK(!iris::ranges::growable_array_writable>); -} - -TEST_CASE("ranges: dummy types") -{ - STATIC_CHECK(iris::ranges::key_value_range>); - STATIC_CHECK(!iris::ranges::mapping_range>); - - STATIC_CHECK(iris::ranges::key_value_range>); - STATIC_CHECK(iris::ranges::mapping_range>); - STATIC_CHECK(!iris::ranges::mapping_container>); - - STATIC_CHECK(iris::ranges::key_value_range>); - STATIC_CHECK(iris::ranges::mapping_range>); - STATIC_CHECK(iris::ranges::mapping_container>); - STATIC_CHECK(!iris::ranges::unique_mapping_container>); - - STATIC_CHECK(iris::ranges::key_value_range>); - STATIC_CHECK(iris::ranges::mapping_range>); - STATIC_CHECK(iris::ranges::mapping_container>); - STATIC_CHECK(iris::ranges::unique_mapping_container>); - - STATIC_CHECK(iris::ranges::growable_array_writable>); - STATIC_CHECK(iris::ranges::fixed_array_writable>); -} +// Test cases involving containers must go to `container_traits.cpp` +// Test cases for dummy ranges / dummy containers must go to `container_traits.cpp` diff --git a/test/run_length_sequence.cpp b/test/run_length_sequence.cpp new file mode 100644 index 0000000..830cc75 --- /dev/null +++ b/test/run_length_sequence.cpp @@ -0,0 +1,554 @@ +// SPDX-License-Identifier: MIT + +#include "iris_test.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace std::string_view_literals; +using namespace std::string_literals; + +TEST_CASE("run_length_sequence: traits") +{ + using RLS = iris::run_length_sequence; + STATIC_CHECK(std::same_as); + STATIC_CHECK(std::same_as); + + STATIC_CHECK(std::same_as>); + STATIC_CHECK(std::is_trivially_copyable_v); + + STATIC_CHECK(std::same_as>); + STATIC_CHECK(std::same_as>); + STATIC_CHECK(std::same_as>); + + STATIC_CHECK(std::indirectly_readable); + STATIC_CHECK(std::bidirectional_iterator); + + STATIC_CHECK(std::indirectly_readable); + STATIC_CHECK(std::bidirectional_iterator); + + STATIC_CHECK(std::ranges::bidirectional_range); + STATIC_CHECK(std::ranges::bidirectional_range); + + STATIC_CHECK(std::sized_sentinel_for); + + STATIC_CHECK(std::default_initializable); + STATIC_CHECK(std::is_copy_constructible_v); + STATIC_CHECK(std::is_copy_assignable_v); + STATIC_CHECK(std::is_move_constructible_v); + STATIC_CHECK(std::is_move_assignable_v); + + { + RLS::value_type iv{}; + auto&& [index, value] = iv; + index = 42; + value = 1.0; + CHECK(iv.index == 42); + CHECK(iv.value == 1.0); + } + { + RLS::value_type iv{}; + auto [index, value] = iv; + index = 42; + value = 1.0; + CHECK(iv.index == 0); + CHECK(iv.value == 0.0); // NOLINT(readability-container-size-empty) + } +} + +TEST_CASE("run_length_sequence: construction") +{ + constexpr auto expected_input = std::array{2, 3, 3, 4, 2, 2, 3, 4, 4}; + auto const expected_runs = std::vector{2, 3, 4, 2, 3, 4}; + + // std::from_range + { + iris::run_length_sequence seq{std::from_range, expected_input}; + auto const expected_elems = std::views::zip(std::views::iota(0u), expected_input) | std::ranges::to(); + auto const actual_elems = seq | std::ranges::to(); + CHECK(expected_elems == actual_elems); + CHECK(seq.run_count() == expected_runs.size()); + CHECK(seq.runs() == expected_runs); + + auto const run_view = seq.run_view(); + auto it = run_view.begin(); + { + REQUIRE(it != run_view.end()); + auto const run_ref = *it++; + CHECK(run_ref.value == 2); + CHECK(run_ref.span.equals({0, 1})); + } + { + REQUIRE(it != run_view.end()); + auto const run_ref = *it++; + CHECK(run_ref.value == 3); + CHECK(run_ref.span.equals({1, 3})); + } + { + REQUIRE(it != run_view.end()); + auto const run_ref = *it++; + CHECK(run_ref.value == 4); + CHECK(run_ref.span.equals({3, 4})); + } + { + REQUIRE(it != run_view.end()); + auto const run_ref = *it++; + CHECK(run_ref.value == 2); + CHECK(run_ref.span.equals({4, 6})); + } + { + REQUIRE(it != run_view.end()); + auto const run_ref = *it++; + CHECK(run_ref.value == 3); + CHECK(run_ref.span.equals({6, 7})); + } + { + REQUIRE(it != run_view.end()); + auto const run_ref = *it++; + CHECK(run_ref.value == 4); + CHECK(run_ref.span.equals({7, 9})); + } + REQUIRE(it == run_view.end()); + } + + // it, se + { + iris::run_length_sequence seq{expected_input.begin(), expected_input.end()}; + auto const expected_elems = std::views::zip(std::views::iota(0u), expected_input) | std::ranges::to(); + auto const actual_elems = seq | std::ranges::to(); + CHECK(expected_elems == actual_elems); + CHECK(seq.run_count() == expected_runs.size()); + CHECK(seq.runs() == expected_runs); + } + + // initializer list + { + iris::run_length_sequence seq{2, 3, 3, 4, 2, 2, 3, 4, 4}; + auto const expected_elems = std::views::zip(std::views::iota(0u), expected_input) | std::ranges::to(); + auto const actual_elems = seq | std::ranges::to(); + CHECK(expected_elems == actual_elems); + CHECK(seq.run_count() == expected_runs.size()); + CHECK(seq.runs() == expected_runs); + } + + // initializer list + append_range + { + iris::run_length_sequence seq{2, 3, 3, 4, 2}; + seq.append_range(std::array{2, 3, 4, 4}); + auto const expected_elems = std::views::zip(std::views::iota(0u), expected_input) | std::ranges::to(); + auto const actual_elems = seq | std::ranges::to(); + CHECK(expected_elems == actual_elems); + CHECK(seq.run_count() == expected_runs.size()); + CHECK(seq.runs() == expected_runs); + } +} + +TEST_CASE("run_length_sequence: emplace_back / push_back / pop_back") +{ + using RLS = iris::run_length_sequence; + + // emplace_back(char array) + { + RLS seq; + auto&& elem = seq.emplace_back("foo"); + CHECK(seq.size() == 1); + CHECK(seq.run_count() == 1); + CHECK(elem.index == 0); + CHECK(elem.value == "foo"); + + seq.pop_back(); + CHECK(seq.empty()); + } + // emplace_back(string_view) + { + RLS seq; + auto&& elem = seq.emplace_back("foo"sv); + CHECK(seq.size() == 1); + CHECK(seq.run_count() == 1); + CHECK(elem.index == 0); + CHECK(elem.value == "foo"); + + seq.pop_back(); + CHECK(seq.empty()); + } + // emplace_back(string) + { + RLS seq; + auto&& elem = seq.emplace_back("foo"s); + CHECK(seq.size() == 1); + CHECK(seq.run_count() == 1); + CHECK(elem.index == 0); + CHECK(elem.value == "foo"); + + seq.pop_back(); + CHECK(seq.empty()); + } + + // push_back(char array) + { + RLS seq; + auto&& elem = seq.push_back("foo"); + CHECK(seq.size() == 1); + CHECK(seq.run_count() == 1); + CHECK(elem.index == 0); + CHECK(elem.value == "foo"); + + seq.pop_back(); + CHECK(seq.empty()); + } + // push_back(string_view) + { + RLS seq; + auto&& elem = seq.push_back("foo"sv); + CHECK(seq.size() == 1); + CHECK(seq.run_count() == 1); + CHECK(elem.index == 0); + CHECK(elem.value == "foo"); + + seq.pop_back(); + CHECK(seq.empty()); + } + // push_back(string) + { + RLS seq; + auto&& elem = seq.push_back("foo"s); + CHECK(seq.size() == 1); + CHECK(seq.run_count() == 1); + CHECK(elem.index == 0); + CHECK(elem.value == "foo"); + + seq.pop_back(); + CHECK(seq.empty()); + } + // push_back(initializer_list) + { + constexpr auto str_arr = std::array{'a', 'b', 'c'}; + + RLS seq; + auto&& elem = seq.push_back({str_arr.begin(), str_arr.end()}); // this cannot be resolved by `emplace_back` + CHECK(seq.size() == 1); + CHECK(seq.run_count() == 1); + CHECK(elem.index == 0); + CHECK(elem.value == "abc"); + + seq.pop_back(); + CHECK(seq.empty()); + } +} + +TEST_CASE("run_length_sequence: sequential insertion") +{ + using RLS = iris::run_length_sequence; + RLS seq; + CHECK(seq == seq); + CHECK(seq.empty()); + CHECK(seq.size() == 0); // NOLINT(readability-container-size-empty) + CHECK(seq.begin() == seq.end()); + CHECK(seq.nth(0) == seq.end()); + + { + using std::swap; + swap(seq, seq); + } + + CHECK_NOTHROW((void)(seq.cbegin() = seq.begin())); + + { + auto&& elem = seq.emplace_back(1.0); + REQUIRE(seq.size() == 1); + REQUIRE(seq.run_count() == 1); + CHECK(elem.index == 0); + CHECK(elem.value == 1.0); + CHECK(seq.front() == elem); + CHECK(seq.back() == elem); + { + auto nth0 = *seq.nth(0); + CHECK(nth0.index == 0); + CHECK(nth0.value == 1.0); + } + CHECK(seq.nth(1) == seq.end()); + + auto it = seq.begin(); + + REQUIRE(it != seq.end()); + auto&& e0 = *it++; + CHECK(e0.index == 0); + CHECK(e0.value == 1.0); + CHECK(seq.front() == elem); + CHECK(seq.back() == e0); + + REQUIRE(it == seq.end()); + } + { + auto&& elem = seq.emplace_back(1.0); + REQUIRE(seq.size() == 2); + REQUIRE(seq.run_count() == 1); + CHECK(elem.index == 1); + CHECK(elem.value == 1.0); + CHECK(seq.back() == elem); + { + auto nth0 = *seq.nth(0); + CHECK(nth0.index == 0); + CHECK(nth0.value == 1.0); + } + { + auto nth1 = *seq.nth(1); + CHECK(nth1.index == 1); + CHECK(nth1.value == 1.0); + } + CHECK(seq.nth(2) == seq.end()); + + auto it = seq.begin(); + + REQUIRE(it != seq.end()); + auto&& e0 = *it++; + CHECK(e0.index == 0); + CHECK(e0.value == 1.0); + + REQUIRE(it != seq.end()); + auto&& e1 = *it++; + CHECK(e1.index == 1); + CHECK(e1.value == 1.0); + + REQUIRE(it == seq.end()); + } + { + auto&& elem = seq.emplace_back(1.1); + REQUIRE(seq.size() == 3); + REQUIRE(seq.run_count() == 2); + CHECK(elem.index == 2); + CHECK(elem.value == 1.1); + CHECK(seq.back() == elem); + { + auto nth0 = *seq.nth(0); + CHECK(nth0.index == 0); + CHECK(nth0.value == 1.0); + } + { + auto nth1 = *seq.nth(1); + CHECK(nth1.index == 1); + CHECK(nth1.value == 1.0); + } + { + auto nth2 = *seq.nth(2); + CHECK(nth2.index == 2); + CHECK(nth2.value == 1.1); + } + CHECK(seq.nth(3) == seq.end()); + + auto it = seq.begin(); + + REQUIRE(it != seq.end()); + auto&& e0 = *it++; + CHECK(e0.index == 0); + CHECK(e0.value == 1.0); + + REQUIRE(it != seq.end()); + auto&& e1 = *it++; + CHECK(e1.index == 1); + CHECK(e1.value == 1.0); + + REQUIRE(it != seq.end()); + auto&& e2 = *it++; + CHECK(e2.index == 2); + CHECK(e2.value == 1.1); + + REQUIRE(it == seq.end()); + } + { + auto&& elem = seq.emplace_back(1.1); + REQUIRE(seq.size() == 4); + REQUIRE(seq.run_count() == 2); + CHECK(elem.index == 3); + CHECK(elem.value == 1.1); + CHECK(seq.back() == elem); + { + auto nth0 = *seq.nth(0); + CHECK(nth0.index == 0); + CHECK(nth0.value == 1.0); + } + { + auto nth1 = *seq.nth(1); + CHECK(nth1.index == 1); + CHECK(nth1.value == 1.0); + } + { + auto nth2 = *seq.nth(2); + CHECK(nth2.index == 2); + CHECK(nth2.value == 1.1); + } + { + auto nth3 = *seq.nth(3); + CHECK(nth3.index == 3); + CHECK(nth3.value == 1.1); + } + CHECK(seq.nth(4) == seq.end()); + + auto it = seq.begin(); + + REQUIRE(it != seq.end()); + auto&& e0 = *it++; + CHECK(e0.index == 0); + CHECK(e0.value == 1.0); + + REQUIRE(it != seq.end()); + auto&& e1 = *it++; + CHECK(e1.index == 1); + CHECK(e1.value == 1.0); + + REQUIRE(it != seq.end()); + auto&& e2 = *it++; + CHECK(e2.index == 2); + CHECK(e2.value == 1.1); + + REQUIRE(it != seq.end()); + auto&& e3 = *it++; + CHECK(e3.index == 3); + CHECK(e3.value == 1.1); + + REQUIRE(it == seq.end()); + } + { + auto&& elem = seq.emplace_back(1.1); + REQUIRE(seq.size() == 5); + REQUIRE(seq.run_count() == 2); + CHECK(elem.index == 4); + CHECK(elem.value == 1.1); + CHECK(seq.back() == elem); + { + auto nth0 = *seq.nth(0); + CHECK(nth0.index == 0); + CHECK(nth0.value == 1.0); + } + { + auto nth1 = *seq.nth(1); + CHECK(nth1.index == 1); + CHECK(nth1.value == 1.0); + } + { + auto nth2 = *seq.nth(2); + CHECK(nth2.index == 2); + CHECK(nth2.value == 1.1); + } + { + auto nth3 = *seq.nth(3); + CHECK(nth3.index == 3); + CHECK(nth3.value == 1.1); + } + { + auto nth4 = *seq.nth(4); + CHECK(nth4.index == 4); + CHECK(nth4.value == 1.1); + } + CHECK(seq.nth(5) == seq.end()); + + auto it = seq.begin(); + + REQUIRE(it != seq.end()); + auto&& e0 = *it++; + CHECK(e0.index == 0); + CHECK(e0.value == 1.0); + + REQUIRE(it != seq.end()); + auto&& e1 = *it++; + CHECK(e1.index == 1); + CHECK(e1.value == 1.0); + + REQUIRE(it != seq.end()); + auto&& e2 = *it++; + CHECK(e2.index == 2); + CHECK(e2.value == 1.1); + + REQUIRE(it != seq.end()); + auto&& e3 = *it++; + CHECK(e3.index == 3); + CHECK(e3.value == 1.1); + + REQUIRE(it != seq.end()); + auto&& e4 = *it++; + CHECK(e4.index == 4); + CHECK(e4.value == 1.1); + + REQUIRE(it == seq.end()); + } + { + auto&& elem = seq.emplace_back(1.2); + REQUIRE(seq.size() == 6); + REQUIRE(seq.run_count() == 3); + CHECK(elem.index == 5); + CHECK(elem.value == 1.2); + CHECK(seq.back() == elem); + { + auto nth0 = *seq.nth(0); + CHECK(nth0.index == 0); + CHECK(nth0.value == 1.0); + } + { + auto nth1 = *seq.nth(1); + CHECK(nth1.index == 1); + CHECK(nth1.value == 1.0); + } + { + auto nth2 = *seq.nth(2); + CHECK(nth2.index == 2); + CHECK(nth2.value == 1.1); + } + { + auto nth3 = *seq.nth(3); + CHECK(nth3.index == 3); + CHECK(nth3.value == 1.1); + } + { + auto nth4 = *seq.nth(4); + CHECK(nth4.index == 4); + CHECK(nth4.value == 1.1); + } + { + auto nth5 = *seq.nth(5); + CHECK(nth5.index == 5); + CHECK(nth5.value == 1.2); + } + CHECK(seq.nth(6) == seq.end()); + + auto it = seq.begin(); + + REQUIRE(it != seq.end()); + auto&& e0 = *it++; + CHECK(e0.index == 0); + CHECK(e0.value == 1.0); + + REQUIRE(it != seq.end()); + auto&& e1 = *it++; + CHECK(e1.index == 1); + CHECK(e1.value == 1.0); + + REQUIRE(it != seq.end()); + auto&& e2 = *it++; + CHECK(e2.index == 2); + CHECK(e2.value == 1.1); + + REQUIRE(it != seq.end()); + auto&& e3 = *it++; + CHECK(e3.index == 3); + CHECK(e3.value == 1.1); + + REQUIRE(it != seq.end()); + auto&& e4 = *it++; + CHECK(e4.index == 4); + CHECK(e4.value == 1.1); + + REQUIRE(it != seq.end()); + auto&& e5 = *it++; + CHECK(e5.index == 5); + CHECK(e5.value == 1.2); + + REQUIRE(it == seq.end()); + } +} diff --git a/test/type_traits.cpp b/test/type_traits.cpp index a489ea5..c30cc89 100644 --- a/test/type_traits.cpp +++ b/test/type_traits.cpp @@ -4,7 +4,7 @@ #include -namespace { +#include template struct tuple; @@ -12,7 +12,6 @@ struct tuple; template struct type_list; - template struct n_tuple; @@ -41,8 +40,24 @@ struct member_ptr_test { }; -} // anonymous - +TEST_CASE("type traits") +{ + STATIC_CHECK(iris::signed_numeric_integral); + STATIC_CHECK(iris::signed_numeric_integral); + STATIC_CHECK(iris::signed_numeric_integral); + STATIC_CHECK(iris::signed_numeric_integral); + STATIC_CHECK(!iris::signed_numeric_integral); + STATIC_CHECK(!iris::signed_numeric_integral); + STATIC_CHECK(!iris::signed_numeric_integral); + + STATIC_CHECK(iris::unsigned_numeric_integral); + STATIC_CHECK(iris::unsigned_numeric_integral); + STATIC_CHECK(iris::unsigned_numeric_integral); + STATIC_CHECK(iris::unsigned_numeric_integral); + STATIC_CHECK(!iris::unsigned_numeric_integral); + STATIC_CHECK(!iris::unsigned_numeric_integral); + STATIC_CHECK(!iris::unsigned_numeric_integral); +} TEST_CASE("is_convertible_without_narrowing: same type identity") {