Enlico
Enlico

Reputation: 28490

Is there a way in Range-v3 to prepend or append an element to a range/view?

Range-v3 has ranges::views::drop and ranges::views::drop_last to remove elements from the front or the back of a view.

Does it offer a similar functions to prepend/append elements to a view?

For now, the shortest way I've found is to concat the range/container with a iota or with a single:

#include <assert.h>
#include <range/v3/view/iota.hpp>
#include <range/v3/view/concat.hpp>
#include <range/v3/to_container.hpp>

using namespace ranges;
using namespace views;
int main() {
    std::vector<int> v{1,2,3};

    auto wi = concat(iota(0,1),v);
    assert(((wi | to_vector) == std::vector<int>{0,1,2,3}));

    auto ws = concat(single(0), v);
    assert(((ws | to_vector) == std::vector<int>{0,1,2,3}));
}

Upvotes: 14

Views: 982

Answers (1)

Vincent X
Vincent X

Reputation: 372

Prepending or appending elements to a view differs from using drop or drop_last in that we must manage the lifetime of the elements to be prepended or appended. There are at least two options. In the straightforward scenario, we could store these elements in a separate container, outside the view adaptors, and utilize concat to achieve the desired outcome. Conversely, if we aim to store the elements in the view adaptors themselves, we must ensure all copies of them share these elements. A feasible (though not optimal) implementation of the prepend_view class, which combines std::shared_ptr, std::vector, and concat_view, is as follows:

template <typename Rng, typename T>
  requires viewable_range<Rng> && input_range<Rng>
struct prepend_view : concat_view<all_t<std::vector<T> &>, Rng> {
private:
  using vector_t = std::vector<T>;
  using concat_view_t = concat_view<all_t<vector_t &>, Rng>;
  std::shared_ptr<vector_t> ptr;

public:
  constexpr prepend_view() = default;
  constexpr prepend_view(Rng rng, std::initializer_list<T> list)
    requires copy_constructible<T>
      : concat_view_t(), ptr(std::make_shared<vector_t>(list)) {
    static_cast<concat_view_t &>(*this) = concat_view_t(*ptr, std::move(rng));
  }
};
template <typename Rng, typename T>
prepend_view(Rng &&, std::initializer_list<T>) -> prepend_view<all_t<Rng>, T>;

Additionally, the function object prepend:

struct prepend_base_fn {
  template <typename Rng, copy_constructible T>
    requires viewable_range<Rng> && input_range<Rng>
  constexpr auto operator()(Rng &&rng, std::initializer_list<T> list) const {
    return prepend_view(rng, list);
  }
};
struct prepend_fn : prepend_base_fn {
  using prepend_base_fn::operator();
  template <copy_constructible T>
  constexpr auto operator()(std::initializer_list<T> list) const {
    return make_view_closure(bind_back(prepend_base_fn(), list));
  }
};
RANGES_INLINE_VARIABLE(prepend_fn, prepend)

The test code [Godbolt]:

int main() {
    int a[] = {1, 2, 3, 4, 5, 6};
    auto v = std::vector{3, 2, 1};
    assert(equal(a, v | prepend({6, 5, 4}) | reverse));
}

append_view and append could be implemented following similar patterns.

Upvotes: 1

Related Questions