diff --git a/include/iris/ranges.hpp b/include/iris/ranges.hpp new file mode 100644 index 0000000..89fd5a8 --- /dev/null +++ b/include/iris/ranges.hpp @@ -0,0 +1,247 @@ +#ifndef IRIS_ZZ_RANGES_HPP +#define IRIS_ZZ_RANGES_HPP + +// SPDX-License-Identifier: MIT + +#include // IWYU pragma: keep + +#include + +#include // IWYU pragma: export +#include +#include +#include + +namespace iris::ranges { + +namespace detail { + +template +using kv_element_t = std::remove_cvref_t>; + +} // detail + +// `R` is a range with value type of 2-element tuple. +// This accepts also array of pair. If stricter check is needed, use `mapping_range`. +// Note: To convert `key_value_range` to `mapping_range`, use `iris::ranges::as_map`. +template +concept key_value_range = + std::ranges::input_range && + requires { + typename detail::kv_element_t; + requires std::tuple_size>::value == 2; + typename std::tuple_element<0, detail::kv_element_t>::type; + typename std::tuple_element<1, detail::kv_element_t>::type; + } && + 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>>; + +template +using range_mapped_t = std::remove_cvref_t>>; + + +// `R` is a range with value type of 2-element tuple AND it has certain +// strictly map-specific traits such as `::key_type` and `::mapped_type`. +// To include more loose type like an array of pair, use `key_value_range`. +template +concept mapping_range = + key_value_range && + requires { + typename std::remove_cvref_t::key_type; + typename std::remove_cvref_t::mapped_type; + } && + 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`. +template + requires ranges::key_value_range +struct as_map_view : std::ranges::view_interface> +{ + R base_ = R{}; + +public: + using key_type = range_key_t; + using mapped_type = range_mapped_t; + + as_map_view() requires std::default_initializable = default; + constexpr explicit as_map_view(R base) : base_(std::move(base)) {} + + [[nodiscard]] constexpr R base() const& requires std::copy_constructible { return base_; } + [[nodiscard]] constexpr R base() && { return std::move(base_); } + + [[nodiscard]] constexpr auto begin() { return std::ranges::begin(base_); } + [[nodiscard]] constexpr auto end() { return std::ranges::end(base_); } + [[nodiscard]] constexpr auto begin() const requires std::ranges::range { return std::ranges::begin(base_); } + [[nodiscard]] constexpr auto end() const requires std::ranges::range { return std::ranges::end(base_); } + + [[nodiscard]] constexpr auto size() requires std::ranges::sized_range { return std::ranges::size(base_); } + [[nodiscard]] constexpr auto size() const requires std::ranges::sized_range { return std::ranges::size(base_); } +}; + +template +as_map_view(R&&) -> as_map_view>; + + +namespace detail { + +struct as_map_fn : std::ranges::range_adaptor_closure +{ + template + requires ranges::key_value_range + [[nodiscard]] static constexpr auto operator()(R&& r) + { + return as_map_view{std::views::all(std::forward(r))}; + } +}; + +} // detail + +// Can be used for making `key_value_range` model `mapping_range`. +[[maybe_unused]] inline constexpr detail::as_map_fn as_map{}; + +} // iris::ranges + +template +inline constexpr bool std::ranges::enable_borrowed_range> + = 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)); + }; + +template +struct dummy_mapping_container +{ + 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&&); +}; + +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 +{ + 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(); +}; + +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 + +#endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index e1565b4..96f1630 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -182,6 +182,7 @@ if(PROJECT_IS_TOP_LEVEL) library_config core type_traits + ranges stdint enum indirect diff --git a/test/ranges.cpp b/test/ranges.cpp new file mode 100644 index 0000000..08d69ba --- /dev/null +++ b/test/ranges.cpp @@ -0,0 +1,252 @@ +// SPDX-License-Identifier: MIT + +#include "iris_test.hpp" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +template +struct FakeNonRangeMap +{ + using key_type = KeyT; + using value_type = std::pair; + using mapped_type = ValueT; +}; + +template +struct FakeRangeMapWithoutMappedType +{ + using key_type = KeyT; + using value_type = std::pair; + + value_type const* begin() const; + value_type const* end() const; +}; + +template +struct RangeMapWith2TupleSize +{ + using key_type = KeyT; + using value_type = std::tuple; // NOT pair + using mapped_type = ValueT; + + value_type const* begin() const; + value_type const* end() const; +}; + +template +struct FakeRangeMapWithWrongTupleSize +{ + using key_type = KeyT; + using value_type = std::tuple; // additional element + using mapped_type = ValueT; + + value_type const* begin() const; + value_type const* end() const; +}; + +template +struct FakeRangeMapWithNonTupleKey +{ + using key_type = KeyT; + using value_type = char; // non-tuple + using mapped_type = ValueT; + + value_type const* begin() const; + value_type const* end() const; +}; + +TEST_CASE("ranges: map related traits") +{ + 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 + { + [[maybe_unused]] constexpr auto filter = std::views::filter([](auto&&) { return true; }); + { + using Map = std::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)>); + + // 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)>); + + // 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)>); + } + { + using PairVec = std::vector>; + + // Map + STATIC_CHECK(iris::ranges::key_value_range())>); + STATIC_CHECK(!iris::ranges::mapping_range())>); + STATIC_CHECK(iris::ranges::mapping_range() | as_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)>); + + // Map& + STATIC_CHECK(iris::ranges::key_value_range())>); + STATIC_CHECK(!iris::ranges::mapping_range())>); + STATIC_CHECK(iris::ranges::mapping_range() | as_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)>); + + // Map const& + STATIC_CHECK(iris::ranges::key_value_range())>); + STATIC_CHECK(!iris::ranges::mapping_range())>); + STATIC_CHECK(iris::ranges::mapping_range() | as_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)>); + } + } + + // --------------------------------------------------- + // Exotic types + + STATIC_CHECK(!iris::ranges::key_value_range>); + + static_assert(std::ranges::forward_range>); + STATIC_CHECK(iris::ranges::key_value_range>); + + static_assert(std::ranges::forward_range>); + STATIC_CHECK(iris::ranges::key_value_range>); + + static_assert(std::ranges::forward_range>); + STATIC_CHECK(!iris::ranges::key_value_range>); + + static_assert(std::ranges::forward_range>); + STATIC_CHECK(!iris::ranges::key_value_range>); + + STATIC_CHECK(!iris::ranges::key_value_range>); + 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>); +}