From 419af63648b98d848346ecdf7ef6e72fc18bd72d Mon Sep 17 00:00:00 2001 From: Nana Sakisaka <1901813+saki7@users.noreply.github.com> Date: Thu, 1 Oct 2026 14:51:00 +0900 Subject: [PATCH] Add more container traits --- include/iris/container_traits.hpp | 199 +++++++++++++++++++++++------- test/container_traits.cpp | 63 ++++++++++ 2 files changed, 220 insertions(+), 42 deletions(-) diff --git a/include/iris/container_traits.hpp b/include/iris/container_traits.hpp index eaa095f..47a0b19 100644 --- a/include/iris/container_traits.hpp +++ b/include/iris/container_traits.hpp @@ -41,6 +41,27 @@ concept unique_mapping_container = }; +namespace detail { + +template +struct element_impl +{ + using type = T; +}; + +template +struct element_impl> +{ + using type = std::pair; +}; + +} // detail + +// The type of an element made before it is added to `ContainerT`: the value type, whose key is not const +template +using element_t = detail::element_impl>::type; + + namespace detail { template @@ -108,32 +129,38 @@ template concept back_accessible = requires(R& r) { back(r); }; namespace detail { template -concept has_emplace_front = - requires(ContainerT& cont) { - cont.emplace_front(std::declval()...); - }; +concept emplaceable = std::constructible_from, Args...>; template -concept has_push_front = - sizeof...(Args) == 1 && - requires(ContainerT& cont) { - cont.push_front(std::declval()...); - }; +concept position_emplaceable = + emplaceable && + !requires { typename std::remove_cvref_t::key_type; }; template -concept has_begin_emplace = - std::ranges::range && - requires(ContainerT& cont) { - cont.emplace(std::ranges::begin(cont), std::declval()...); - }; +concept has_emplace_front = requires(ContainerT& cont) { + requires emplaceable; + cont.emplace_front(std::declval()...); +}; template -concept has_begin_insert = - sizeof...(Args) == 1 && - std::ranges::range && - requires(ContainerT& cont) { - cont.insert(std::ranges::begin(cont), std::declval()...); - }; +concept has_push_front = requires(ContainerT& cont) { + requires sizeof...(Args) == 1; + cont.push_front(std::declval()...); +}; + +template +concept has_begin_emplace = requires(ContainerT& cont) { + requires std::ranges::range; + requires position_emplaceable; + cont.emplace(std::ranges::begin(cont), std::declval()...); +}; + +template +concept has_begin_insert = requires(ContainerT& cont) { + requires sizeof...(Args) == 1; + requires std::ranges::range; + cont.insert(std::ranges::begin(cont), std::declval()...); +}; } // detail @@ -162,7 +189,10 @@ concept prependable = front_pushable || detail::has_begin_emplace || detail::has_begin_insert || - (sizeof...(Args) == 0 && default_prependable); + requires { + requires sizeof...(Args) == 0; + requires default_prependable; + }; namespace detail { @@ -296,32 +326,30 @@ struct prepend_fn namespace detail { template -concept has_emplace_back = - requires(ContainerT& cont) { - cont.emplace_back(std::declval()...); - }; +concept has_emplace_back = requires(ContainerT& cont) { + requires emplaceable; + cont.emplace_back(std::declval()...); +}; template -concept has_push_back = - sizeof...(Args) == 1 && - requires(ContainerT& cont) { - cont.push_back(std::declval()...); - }; +concept has_push_back = requires(ContainerT& cont) { + requires sizeof...(Args) == 1; + cont.push_back(std::declval()...); +}; template -concept has_end_emplace = - std::ranges::range && - requires(ContainerT& cont) { - cont.emplace(std::ranges::end(cont), std::declval()...); - }; +concept has_end_emplace = requires(ContainerT& cont) { + requires std::ranges::range; + requires position_emplaceable; + 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()...); - }; +concept has_end_insert = requires(ContainerT& cont) { + requires sizeof...(Args) == 1; + requires std::ranges::range; + cont.insert(std::ranges::end(cont), std::declval()...); +}; } // detail @@ -350,7 +378,10 @@ concept appendable = back_pushable || detail::has_end_emplace || detail::has_end_insert || - (sizeof...(Args) == 0 && default_appendable); + requires { + requires sizeof...(Args) == 0; + requires default_appendable; + }; namespace detail { @@ -614,6 +645,90 @@ concept compatible_range = std::ranges::input_range && std::convertible_to, ElemT>; +// ------------------------------------------------------------ + +namespace detail { + +template +concept has_append_range = requires(ContainerT& cont, R&& r) { + cont.append_range(std::forward(r)); +}; + +template +concept has_end_insert_range = + std::ranges::range && + requires(ContainerT& cont, R&& r) { + cont.insert_range(std::ranges::end(cont), std::forward(r)); + }; + +template +concept has_insert_range = requires(ContainerT& cont, R&& r) { + cont.insert_range(std::forward(r)); +}; + +template +concept has_end_insert_iterators = + std::ranges::range && + std::ranges::common_range && + requires(ContainerT& cont, R& r) { + cont.insert(std::ranges::end(cont), std::ranges::begin(r), std::ranges::end(r)); + }; + +template +concept has_insert_iterators = + std::ranges::common_range && + requires(ContainerT& cont, R& r) { + cont.insert(std::ranges::begin(r), std::ranges::end(r)); + }; + +// The elements of `r` at the end of `cont`, or into the associative `cont` +struct append_range_fn +{ + template + requires + compatible_range> && + ( + has_append_range || + has_end_insert_range || + has_insert_range || + has_end_insert_iterators || + has_insert_iterators + ) + static constexpr void operator()(ContainerT& cont, R&& r) + { + if constexpr (has_append_range) { + cont.append_range(std::forward(r)); + + } else if constexpr (has_end_insert_range) { + cont.insert_range(std::ranges::end(cont), std::forward(r)); + + } else if constexpr (has_insert_range) { + cont.insert_range(std::forward(r)); + + } else if constexpr (has_end_insert_iterators) { + cont.insert(std::ranges::end(cont), std::ranges::begin(r), std::ranges::end(r)); + + } else { + cont.insert(std::ranges::begin(r), std::ranges::end(r)); + } + } +}; + +struct clear_fn +{ + template + requires requires(ContainerT& cont) { cont.clear(); } + static constexpr void operator()(ContainerT& cont) noexcept(noexcept(cont.clear())) + { + cont.clear(); + } +}; + +} // detail + +[[maybe_unused]] inline constexpr detail::append_range_fn append_range{}; +[[maybe_unused]] inline constexpr detail::clear_fn clear{}; + } // iris::container namespace iris::container::dummy { diff --git a/test/container_traits.cpp b/test/container_traits.cpp index 4fdfb88..0590bf0 100644 --- a/test/container_traits.cpp +++ b/test/container_traits.cpp @@ -798,3 +798,66 @@ TEST_CASE("container: append") STATIC_CHECK(!std::invocable); } } + +TEST_CASE("container: append into associative containers") +{ + // `emplace(it, x)` of an associative container is a valid expression but not a valid call + STATIC_CHECK(iris::container::appendable, int>); + STATIC_CHECK(iris::container::growable_array>); + + std::set s; + iris::container::append(s, 2); + iris::container::append(s, 1); + CHECK(s == std::set{1, 2}); + + std::map m; + iris::container::append(m, std::pair{1, 10}); + CHECK(m == std::map{{1, 10}}); +} + +TEST_CASE("container: append_range") +{ + std::vector source{"a", "b"}; + + std::vector v{"x"}; + iris::container::append_range(v, source); + CHECK(v == std::vector{"x", "a", "b"}); + + std::vector moved{"x"}; + iris::container::append_range(moved, source | std::views::as_rvalue); + CHECK(moved == std::vector{"x", "a", "b"}); + CHECK(source == std::vector{"", ""}); + + std::string str = "x"; + iris::container::append_range(str, "ab"sv); + CHECK(str == "xab"); + + std::set s{3}; + iris::container::append_range(s, std::vector{2, 1}); + CHECK(s == std::set{1, 2, 3}); + + std::map m{{1, 10}}; + iris::container::append_range(m, std::vector>{{2, 20}}); + CHECK(m == std::map{{1, 10}, {2, 20}}); + + // `std::vector(std::size_t)` is not a conversion + STATIC_CHECK(!std::invocable>&, std::vector>); + STATIC_CHECK(!std::invocable&, std::vector>); +} + +TEST_CASE("container: clear") +{ + std::vector v{1, 2}; + iris::container::clear(v); + CHECK(v.empty()); + + STATIC_CHECK(!std::invocable&>); + STATIC_CHECK(!std::invocable const&>); +} + +TEST_CASE("container: element_t") +{ + STATIC_CHECK(std::same_as>, int>); + STATIC_CHECK(std::same_as>, std::pair>); + STATIC_CHECK(std::same_as>, int>); +}