Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
199 changes: 157 additions & 42 deletions include/iris/container_traits.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,27 @@ concept unique_mapping_container =
};


namespace detail {

template<class T>
struct element_impl
{
using type = T;
};

template<class K, class V>
struct element_impl<std::pair<K const, V>>
{
using type = std::pair<K, V>;
};

} // detail

// The type of an element made before it is added to `ContainerT`: the value type, whose key is not const
template<class ContainerT>
using element_t = detail::element_impl<std::ranges::range_value_t<ContainerT>>::type;


namespace detail {

template<class ContainerT>
Expand Down Expand Up @@ -108,32 +129,38 @@ template<class R> concept back_accessible = requires(R& r) { back(r); };
namespace detail {

template<class ContainerT, class... Args>
concept has_emplace_front =
requires(ContainerT& cont) {
cont.emplace_front(std::declval<Args>()...);
};
concept emplaceable = std::constructible_from<std::ranges::range_value_t<ContainerT>, Args...>;

template<class ContainerT, class... Args>
concept has_push_front =
sizeof...(Args) == 1 &&
requires(ContainerT& cont) {
cont.push_front(std::declval<Args>()...);
};
concept position_emplaceable =
emplaceable<ContainerT, Args...> &&
!requires { typename std::remove_cvref_t<ContainerT>::key_type; };

template<class ContainerT, class... Args>
concept has_begin_emplace =
std::ranges::range<ContainerT> &&
requires(ContainerT& cont) {
cont.emplace(std::ranges::begin(cont), std::declval<Args>()...);
};
concept has_emplace_front = requires(ContainerT& cont) {
requires emplaceable<ContainerT, Args...>;
cont.emplace_front(std::declval<Args>()...);
};

template<class ContainerT, class... Args>
concept has_begin_insert =
sizeof...(Args) == 1 &&
std::ranges::range<ContainerT> &&
requires(ContainerT& cont) {
cont.insert(std::ranges::begin(cont), std::declval<Args>()...);
};
concept has_push_front = requires(ContainerT& cont) {
requires sizeof...(Args) == 1;
cont.push_front(std::declval<Args>()...);
};

template<class ContainerT, class... Args>
concept has_begin_emplace = requires(ContainerT& cont) {
requires std::ranges::range<ContainerT>;
requires position_emplaceable<ContainerT, Args...>;
cont.emplace(std::ranges::begin(cont), std::declval<Args>()...);
};

template<class ContainerT, class... Args>
concept has_begin_insert = requires(ContainerT& cont) {
requires sizeof...(Args) == 1;
requires std::ranges::range<ContainerT>;
cont.insert(std::ranges::begin(cont), std::declval<Args>()...);
};

} // detail

Expand Down Expand Up @@ -162,7 +189,10 @@ concept prependable =
front_pushable<ContainerT, Args...> ||
detail::has_begin_emplace<ContainerT, Args...> ||
detail::has_begin_insert<ContainerT, Args...> ||
(sizeof...(Args) == 0 && default_prependable<ContainerT>);
requires {
requires sizeof...(Args) == 0;
requires default_prependable<ContainerT>;
};

namespace detail {

Expand Down Expand Up @@ -296,32 +326,30 @@ struct prepend_fn
namespace detail {

template<class ContainerT, class... Args>
concept has_emplace_back =
requires(ContainerT& cont) {
cont.emplace_back(std::declval<Args>()...);
};
concept has_emplace_back = requires(ContainerT& cont) {
requires emplaceable<ContainerT, Args...>;
cont.emplace_back(std::declval<Args>()...);
};

template<class ContainerT, class... Args>
concept has_push_back =
sizeof...(Args) == 1 &&
requires(ContainerT& cont) {
cont.push_back(std::declval<Args>()...);
};
concept has_push_back = requires(ContainerT& cont) {
requires sizeof...(Args) == 1;
cont.push_back(std::declval<Args>()...);
};

template<class ContainerT, class... Args>
concept has_end_emplace =
std::ranges::range<ContainerT> &&
requires(ContainerT& cont) {
cont.emplace(std::ranges::end(cont), std::declval<Args>()...);
};
concept has_end_emplace = requires(ContainerT& cont) {
requires std::ranges::range<ContainerT>;
requires position_emplaceable<ContainerT, Args...>;
cont.emplace(std::ranges::end(cont), std::declval<Args>()...);
};

template<class ContainerT, class... Args>
concept has_end_insert =
sizeof...(Args) == 1 &&
std::ranges::range<ContainerT> &&
requires(ContainerT& cont) {
cont.insert(std::ranges::end(cont), std::declval<Args>()...);
};
concept has_end_insert = requires(ContainerT& cont) {
requires sizeof...(Args) == 1;
requires std::ranges::range<ContainerT>;
cont.insert(std::ranges::end(cont), std::declval<Args>()...);
};

} // detail

Expand Down Expand Up @@ -350,7 +378,10 @@ concept appendable =
back_pushable<ContainerT, Args...> ||
detail::has_end_emplace<ContainerT, Args...> ||
detail::has_end_insert<ContainerT, Args...> ||
(sizeof...(Args) == 0 && default_appendable<ContainerT>);
requires {
requires sizeof...(Args) == 0;
requires default_appendable<ContainerT>;
};

namespace detail {

Expand Down Expand Up @@ -614,6 +645,90 @@ concept compatible_range =
std::ranges::input_range<R> &&
std::convertible_to<std::ranges::range_reference_t<R>, ElemT>;

// ------------------------------------------------------------

namespace detail {

template<class ContainerT, class R>
concept has_append_range = requires(ContainerT& cont, R&& r) {
cont.append_range(std::forward<R>(r));
};

template<class ContainerT, class R>
concept has_end_insert_range =
std::ranges::range<ContainerT> &&
requires(ContainerT& cont, R&& r) {
cont.insert_range(std::ranges::end(cont), std::forward<R>(r));
};

template<class ContainerT, class R>
concept has_insert_range = requires(ContainerT& cont, R&& r) {
cont.insert_range(std::forward<R>(r));
};

template<class ContainerT, class R>
concept has_end_insert_iterators =
std::ranges::range<ContainerT> &&
std::ranges::common_range<R> &&
requires(ContainerT& cont, R& r) {
cont.insert(std::ranges::end(cont), std::ranges::begin(r), std::ranges::end(r));
};

template<class ContainerT, class R>
concept has_insert_iterators =
std::ranges::common_range<R> &&
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<class ContainerT, class R>
requires
compatible_range<R, std::ranges::range_value_t<ContainerT>> &&
(
has_append_range<ContainerT, R> ||
has_end_insert_range<ContainerT, R> ||
has_insert_range<ContainerT, R> ||
has_end_insert_iterators<ContainerT, R> ||
has_insert_iterators<ContainerT, R>
)
static constexpr void operator()(ContainerT& cont, R&& r)
{
if constexpr (has_append_range<ContainerT, R>) {
cont.append_range(std::forward<R>(r));

} else if constexpr (has_end_insert_range<ContainerT, R>) {
cont.insert_range(std::ranges::end(cont), std::forward<R>(r));

} else if constexpr (has_insert_range<ContainerT, R>) {
cont.insert_range(std::forward<R>(r));

} else if constexpr (has_end_insert_iterators<ContainerT, R>) {
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<class ContainerT>
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 {
Expand Down
63 changes: 63 additions & 0 deletions test/container_traits.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -798,3 +798,66 @@ TEST_CASE("container: append")
STATIC_CHECK(!std::invocable<decltype(iris::container::append_return), Cont&, int, int>);
}
}

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<std::set<int>, int>);
STATIC_CHECK(iris::container::growable_array<std::map<int, int>>);

std::set<int> s;
iris::container::append(s, 2);
iris::container::append(s, 1);
CHECK(s == std::set<int>{1, 2});

std::map<int, int> m;
iris::container::append(m, std::pair{1, 10});
CHECK(m == std::map<int, int>{{1, 10}});
}

TEST_CASE("container: append_range")
{
std::vector<std::string> source{"a", "b"};

std::vector<std::string> v{"x"};
iris::container::append_range(v, source);
CHECK(v == std::vector<std::string>{"x", "a", "b"});

std::vector<std::string> moved{"x"};
iris::container::append_range(moved, source | std::views::as_rvalue);
CHECK(moved == std::vector<std::string>{"x", "a", "b"});
CHECK(source == std::vector<std::string>{"", ""});

std::string str = "x";
iris::container::append_range(str, "ab"sv);
CHECK(str == "xab");

std::set<int> s{3};
iris::container::append_range(s, std::vector{2, 1});
CHECK(s == std::set<int>{1, 2, 3});

std::map<int, int> m{{1, 10}};
iris::container::append_range(m, std::vector<std::pair<int, int>>{{2, 20}});
CHECK(m == std::map<int, int>{{1, 10}, {2, 20}});

// `std::vector<int>(std::size_t)` is not a conversion
STATIC_CHECK(!std::invocable<decltype(iris::container::append_range), std::vector<std::vector<int>>&, std::vector<int>>);
STATIC_CHECK(!std::invocable<decltype(iris::container::append_range), std::array<int, 1>&, std::vector<int>>);
}

TEST_CASE("container: clear")
{
std::vector<int> v{1, 2};
iris::container::clear(v);
CHECK(v.empty());

STATIC_CHECK(!std::invocable<decltype(iris::container::clear), std::array<int, 1>&>);
STATIC_CHECK(!std::invocable<decltype(iris::container::clear), std::vector<int> const&>);
}

TEST_CASE("container: element_t")
{
STATIC_CHECK(std::same_as<iris::container::element_t<std::vector<int>>, int>);
STATIC_CHECK(std::same_as<iris::container::element_t<std::map<int, std::string>>, std::pair<int, std::string>>);
STATIC_CHECK(std::same_as<iris::container::element_t<std::set<int>>, int>);
}
Loading