Program Listing for File tuple_vector.hpp
↰ Return to documentation for file (include/beluga/containers/tuple_vector.hpp
)
// Copyright 2022-2024 Ekumen, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef BELUGA_CONTAINERS_TUPLE_VECTOR_HPP
#define BELUGA_CONTAINERS_TUPLE_VECTOR_HPP
#include <tuple>
#include <vector>
#include <beluga/type_traits.hpp>
#include <beluga/views/zip.hpp>
#include <range/v3/algorithm/copy.hpp>
#include <range/v3/iterator/insert_iterators.hpp>
#include <range/v3/view/const.hpp>
#include <range/v3/view/take.hpp>
namespace beluga {
template <template <class> class InternalContainer, class T>
class TupleContainer;
template <template <class> class InternalContainer, class... Types>
class TupleContainer<InternalContainer, std::tuple<Types...>> {
public:
using value_type = std::tuple<Types...>;
using reference_type = ranges::common_tuple<Types&...>;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
using allocator_type = void;
constexpr TupleContainer() = default;
explicit constexpr TupleContainer(size_type count) : sequences_{((void)sizeof(Types), count)...} {}
template <typename I, typename S>
constexpr TupleContainer(I first, S last) {
assign(first, last);
}
constexpr TupleContainer(std::initializer_list<value_type> init) { assign(init.begin(), init.end()); }
[[nodiscard]] constexpr bool empty() const noexcept { return std::get<0>(sequences_).empty(); }
[[nodiscard]] constexpr size_type size() const noexcept { return std::get<0>(sequences_).size(); }
[[nodiscard]] constexpr size_type capacity() const noexcept { return std::get<0>(sequences_).capacity(); }
constexpr void clear() noexcept {
std::apply([](auto&&... containers) { (containers.clear(), ...); }, sequences_);
}
template <typename I, typename S>
constexpr void assign(I first, S last) {
auto range = ranges::make_subrange(first, last);
static_assert(ranges::input_range<decltype(range)>);
if constexpr (ranges::sized_range<decltype(range)>) {
resize(ranges::size(range));
ranges::copy(range, begin());
} else {
// Optimization to copy as much as we can before we start back inserting.
// Back insertion time is severely punished by having multiple containers.
//
// There are two bad cases for this implementation:
// - The capacity is too large compared to the size of the input range, so
// we allocate and default construct unnecessarily.
// - The capacity is too low compared to the size of the input range, so
// there will be a lot of back inserting.
//
// If common guidelines of calling reserve with the expected size are followed,
// then this is quite fast, even when we don't know the exact size of the range
// in advance.
resize(capacity());
// Copy elements until we reach current capacity.
auto limited_range = range | ranges::views::take(size());
auto [last_in, last_out] = ranges::copy(limited_range, begin());
const auto copied_size = static_cast<size_type>(ranges::distance(begin(), last_out));
if (size() == copied_size) {
// Back insert the remaining elements if any.
ranges::copy(last_in.base(), last, ranges::back_inserter(*this));
} else {
// Remove extra elements by resizing to the correct size.
resize(copied_size);
}
}
}
template <typename R>
constexpr void assign_range(R&& range) {
assign(ranges::begin(range), ranges::end(range));
}
constexpr void reserve(size_type new_cap) {
std::apply([new_cap](auto&&... containers) { (containers.reserve(new_cap), ...); }, sequences_);
}
constexpr void resize(size_type count) {
std::apply([count](auto&&... containers) { (containers.resize(count), ...); }, sequences_);
}
constexpr void push_back(const value_type& value) {
push_back_impl(value, std::make_index_sequence<sizeof...(Types)>());
}
constexpr void push_back(value_type&& value) {
push_back_impl(std::move(value), std::make_index_sequence<sizeof...(Types)>());
}
[[nodiscard]] constexpr auto begin() const { return all().begin(); }
[[nodiscard]] constexpr auto end() const { return all().end(); }
[[nodiscard]] constexpr auto begin() { return all().begin(); }
[[nodiscard]] constexpr auto end() { return all().end(); }
private:
std::tuple<InternalContainer<Types>...> sequences_;
template <typename T, std::size_t... Ids>
constexpr void push_back_impl(T value, std::index_sequence<Ids...>) {
(std::get<Ids>(sequences_).push_back(std::get<Ids>(value)), ...);
}
[[nodiscard]] constexpr auto all() const {
return std::apply([](auto&... containers) { return beluga::views::zip(containers...); }, sequences_);
}
[[nodiscard]] constexpr auto all() {
return std::apply([](auto&... containers) { return beluga::views::zip(containers...); }, sequences_);
}
};
template <class T>
using Vector = std::vector<T, std::allocator<T>>;
template <class T>
class TupleVector : public TupleContainer<Vector, T> {
using TupleContainer<Vector, T>::TupleContainer;
};
template <class I, class S, typename = std::enable_if_t<ranges::input_iterator<I> && ranges::input_iterator<S>>>
TupleVector(I, S) -> TupleVector<decay_tuple_like_t<ranges::iter_value_t<I>>>;
} // namespace beluga
#endif