Humam Helfawi
Humam Helfawi

Reputation: 20311

Reserving sub vectors while constructing

I have this:

size_t n = 100;
std::vector<std::vector<foo>> v(n);

The count of the sub vectors is dynamic but known. However, the number of items in each vector is not known but I have an estimation about it so I want to reserve the sub vectors before start pushing back into them. What I am currently doing is:

size_t estimated_size = 1000;
for (auto& sub_vector: v){
   sub_vector.reserve(estimated_size);
}

Is there a better way? Like doing it while constructing?

P.S. This is not an option:

size_t n = 100;
size_t estimated_size = 1000;
std::vector<std::vector<foo>> v(n, std::vector<foo>(estimated_size));

I just want to reserve without constructing because foo is costy to be constructed twice.

Upvotes: 10

Views: 215

Answers (3)

Barry
Barry

Reputation: 304132

Piggy-packing off of Yakk's answer here in a way that doesn't really involve writing my own code. With Ranges-v3, we can do this directly by just building a range of vectors of the correct capacity. I also find this pretty easy to read:

std::vector<std::vector<int>> v = 
    view::ints(0, 100)
    | view::transform([](int ) {
        std::vector<int> sub_v;
        sub_v.reserve(100);
        return sub_v;
    });

Upvotes: 1

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275966

Here is a quick countdown iterator:

template<class F,
  class T=std::result_of_t<F const&(std::size_t const&)>
>
struct countdown_iterator:
  std::iterator<
    std::input_iterator_tag,
    T,
    std::ptrdiff_t,
    T*,
    T
  >
{
  using self=countdown_iterator;
  std::size_t count_down = 0;
  F f;
  T operator*() const {
    return f(count_down);
  }
  self& operator++() {
    --count_down;
    return *this;
  }
  self operator++(int) {
    auto result = *this;
    ++(*this);
    return result;
  }
  friend bool operator==(self const& lhs, self const& rhs) {
    return lhs.count_down == rhs.count_down;
  }
  friend bool operator!=(self const& lhs, self const& rhs) {
    return !(lhs==rhs);
  }
};

a half-assed range class:

template<class It>
struct range {
  It b, e;
  It begin() const { return b; }
  It end() const { return e; }
  bool empty() const { return begin()==end(); }
  decltype(auto) front() const { return *begin(); }
  range():b(),e() {}
  range(It s, It f):b(s), e(f) {}
  range(range const&)=default;
  range& operator=(range const&)=default;
  ~range() = default;

  template<class C,
    class=std::enable_if_t<!std::is_same<std::decay_t<C>, range>>
  >
  range( C&& c ):
    range(std::begin(std::forward<C>(c)), std::end(std::forward<C>(c)))
  {}
};
template<class It>
range<It> make_range( It b, It e ) { return {std::move(b),std::move(e)}; };

and then we can count:

template<class F,
  class dF=std::decay_t<F>,
  class It=countdown_iterator<dF>
  class R=range<It>
>
R countdown( std::size_t N, F&& f ) {
  countdown_iterator e( N, f ):
  countdown_iterator b( N, std::forward<F>(f) );
  return {std::move(b),std::move(e)};
}

use:

size_t n = 100;

size_t m = 1000;
auto src = countdown(
  n,
  [m](auto&&){ std::vector<foo> v; v.reserve(m); return v; }
);
std::vector<std::vector<foo>> v;
v.reserve(100);
v.insert(v.end(), src.begin(), src.end() );

here we create a countdown "input" iterator that runs for 100 iterators. Each time you dereference it it returns a vector with m capacity.

Upvotes: 2

user3467895
user3467895

Reputation: 602

If you really want to do it while constructing the vector you could use the constructor that takes two iterators and provide your own custom iterator. Dereferencing the iterator would create a vector reserve it and then return it:

class VectorReserveItr : public std::iterator<std::input_iterator_tag, foo> {
  size_t i;
  size_t capacity;
public:
  VectorReserveItr(size_t i, size_t capacity) : i(i), capacity(capacity) {}
  VectorReserveItr& operator++() { ++i; return *this; }
  bool operator!=(const VectorReserveItr& rhs) { return i != rhs.i; }
  std::vector<foo> operator*() {
      std::vector<foo> ret;
      ret.reserve(capacity);
      return ret;
  }
};

std::vector<std::vector<foo>> v(VectorReserveItr(0, 1000), VectorReserveItr(100, 1000));

But I wouldn't expect it to be faster than a loop and I don't think it is more readable either.

Live demo.

Upvotes: 3

Related Questions