Workaround MSVC limitation in SmallVector_
[xapian.git] / xapian-core / api / smallvector.h
blob51bc22d844cb2b9174f9f78ae63ee6876def0b3b
1 /** @file smallvector.h
2 * @brief Custom vector implementations using small vector optimisation
3 */
4 /* Copyright (C) 2012,2013,2014,2017 Olly Betts
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to
8 * deal in the Software without restriction, including without limitation the
9 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10 * sell copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22 * IN THE SOFTWARE.
25 #ifndef XAPIAN_INCLUDED_SMALLVECTOR_H
26 #define XAPIAN_INCLUDED_SMALLVECTOR_H
28 #include <algorithm>
29 #include <cstddef> // For std::size_t
30 #include <cstring> // For std::memcpy
31 #include <type_traits>
33 namespace Xapian {
35 /** Suitable for "simple" type T.
37 * If sizeof(T) > 2 * sizeof(T*) this isn't going to work, and it's not very
38 * useful when sizeof(T) == 2 * sizeof(T*) as you can only store a single
39 * element inline.
41 * Offers optional Copy-On-Write functionality - if COW is true, then copying
42 * a Vec with external data only makes a copy of that data if you attempt
43 * to modify it. Current COW is only supported for integral types T.
45 template<typename T,
46 bool COW = false,
47 typename = typename std::enable_if<!COW ||
48 std::is_integral<T>::value>::type>
49 class Vec {
50 std::size_t c;
52 static constexpr std::size_t INTERNAL_CAPACITY = 2 * sizeof(T*) / sizeof(T);
54 union {
55 T v[INTERNAL_CAPACITY];
56 struct {
57 T* b;
58 T* e;
60 } u;
62 struct Vec_to_copy {
63 const Vec& ref;
64 explicit Vec_to_copy(const Vec& o) : ref(o) {}
67 public:
68 typedef std::size_t size_type;
70 typedef const T* const_iterator;
72 typedef T* iterator;
74 Vec() : c(0) { }
76 // Prevent inadvertent copying.
77 Vec(const Vec&) = delete;
79 Vec(const Vec_to_copy& o) {
80 do_copy_from(o.ref);
83 // Prevent inadvertent copying.
84 void operator=(const Vec&) = delete;
86 void operator=(const Vec_to_copy& o) {
87 clear();
88 do_copy_from(o.ref);
91 Vec_to_copy copy() const {
92 return Vec_to_copy(*this);
95 Vec(Vec&& o) noexcept : Vec() {
96 std::memcpy(&u, &o.u, sizeof(u));
97 std::swap(c, o.c);
100 void operator=(Vec&& o) {
101 clear();
102 u = o.u;
103 std::swap(c, o.c);
106 explicit Vec(size_type n) : c(0) {
107 reserve(n);
110 ~Vec() {
111 clear();
114 size_type size() const {
115 return is_external() ? u.e - u.b : c;
118 size_type capacity() const {
119 return is_external() ? c : INTERNAL_CAPACITY;
122 bool empty() const {
123 return is_external() ? u.b == u.e : c == 0;
126 void reserve(size_type n) {
127 if (n > capacity()) {
128 do_reserve(n);
132 const_iterator cbegin() const {
133 return is_external() ? u.b : u.v;
136 const_iterator cend() const {
137 return is_external() ? u.e : u.v + c;
140 const_iterator begin() const {
141 return cbegin();
144 const_iterator end() const {
145 return cend();
148 iterator begin() {
149 // FIXME: This is a bit eager - non-const begin() is often invoked when
150 // no modification is needed, but doing it lazily is a bit tricky as
151 // the pointer will change when we COW.
152 if (COW && is_external() && u.b[-1] > 0) {
153 do_cow();
155 return is_external() ? u.b : u.v;
158 iterator end() {
159 if (COW && is_external() && u.b[-1] > 0) {
160 do_cow();
162 return is_external() ? u.e : u.v + c;
165 void push_back(T elt) {
166 auto cap = capacity();
167 if (size() == cap) {
168 do_reserve(cap * 2);
170 if (c >= INTERNAL_CAPACITY) {
171 if (COW && u.b[-1] > 0)
172 do_cow();
173 *(u.e++) = elt;
174 } else {
175 u.v[c++] = elt;
179 void pop_back() {
180 if (is_external()) {
181 --u.e;
182 } else {
183 --c;
187 void clear() {
188 if (is_external())
189 do_free();
190 c = 0;
193 void erase(const_iterator it) {
194 auto end_it = end();
195 while (true) {
196 T* p = const_cast<T*>(it);
197 ++it;
198 if (it == end_it)
199 break;
200 *p = *it;
202 if (is_external()) {
203 --u.e;
204 } else {
205 --c;
209 void insert(const_iterator pos, const T& elt) {
210 push_back(T());
211 T* p = const_cast<T*>(end());
212 while (--p != pos) {
213 *p = p[-1];
215 *(const_cast<T*>(pos)) = elt;
218 const T& operator[](size_type idx) const {
219 return begin()[idx];
222 T& operator[](size_type idx) {
223 if (COW && is_external() && u.b[-1] > 0) {
224 do_cow();
226 return const_cast<T&>(begin()[idx]);
229 const T& front() const {
230 return *(begin());
233 const T& back() const {
234 return end()[-1];
237 protected:
238 void do_free() {
239 if (!COW || u.b[-1] == 0)
240 delete [] (u.b - COW);
241 else
242 --u.b[-1];
245 void do_reserve(size_type n) {
246 // Logic error or size_t wrapping.
247 if (rare(COW ? n < c : n <= c))
248 throw std::bad_alloc();
249 T* blk = new T[n + COW];
250 if (COW)
251 *blk++ = 0;
252 if (is_external()) {
253 u.e = std::copy(u.b, u.e, blk);
254 do_free();
255 } else {
256 u.e = std::copy(u.v, u.v + c, blk);
258 u.b = blk;
259 c = n;
262 void do_cow() {
263 T* blk = new T[c + 1];
264 *blk++ = 0;
265 u.e = std::copy(u.b, u.e, blk);
266 --u.b[-1];
267 u.b = blk;
270 void do_copy_from(const Vec& o) {
271 if (!o.is_external()) {
272 u = o.u;
273 c = o.c;
274 } else if (COW) {
275 u = o.u;
276 c = o.c;
277 ++u.b[-1];
278 } else {
279 T* blk = new T[o.c];
280 u.e = std::copy(o.u.b, o.u.e, blk);
281 u.b = blk;
282 c = o.c;
286 /// Return true if storage is external to the object.
287 bool is_external() const noexcept {
288 return c > INTERNAL_CAPACITY;
292 template<typename T>
293 using VecCOW = Vec<T, true>;
295 class SmallVector_ {
296 std::size_t c;
298 static constexpr std::size_t INTERNAL_CAPACITY = 2;
300 void * p[INTERNAL_CAPACITY];
302 public:
303 SmallVector_() : c(0) { }
305 // Prevent inadvertent copying.
306 SmallVector_(const SmallVector_&) = delete;
308 // Prevent inadvertent copying.
309 void operator=(const SmallVector_&) = delete;
311 SmallVector_(SmallVector_&& o) noexcept : SmallVector_() {
312 std::memcpy(p, o.p, sizeof(p));
313 std::swap(c, o.c);
316 explicit SmallVector_(std::size_t n) : c(0) {
317 reserve(n);
320 std::size_t size() const {
321 if (!is_external())
322 return c;
323 void * const * b = static_cast<void * const *>(p[0]);
324 void * const * e = static_cast<void * const *>(p[1]);
325 return e - b;
328 std::size_t capacity() const {
329 return is_external() ? c : INTERNAL_CAPACITY;
332 bool empty() const {
333 return is_external() ? p[0] == p[1] : c == 0;
336 void reserve(std::size_t n) {
337 if (n > INTERNAL_CAPACITY && n > c) {
338 do_reserve(n);
342 protected:
343 void do_free();
345 void do_reserve(std::size_t n);
347 void do_clear() {
348 if (is_external())
349 do_free();
350 c = 0;
353 /// Return true if storage is external to the object.
354 bool is_external() const {
355 return c > INTERNAL_CAPACITY;
358 void * const * do_begin() const {
359 return is_external() ? static_cast<void * const *>(p[0]) : p;
362 void * const * do_end() const {
363 return is_external() ? static_cast<void * const *>(p[1]) : p + c;
366 void do_push_back(void* elt) {
367 std::size_t cap = capacity();
368 if (size() == cap) {
369 do_reserve(cap * 2);
371 if (c >= INTERNAL_CAPACITY) {
372 void ** e = static_cast<void **>(p[1]);
373 *e++ = elt;
374 p[1] = static_cast<void*>(e);
375 } else {
376 p[c++] = elt;
381 /** Vector of Xapian PIMPL internal objects.
383 * A notable feature is that if the vector holds <= 2 objects, there's no
384 * extra storage - the two internal pointers are held in the two
385 * pointers which otherwise point to the first and just after the last
386 * element.
388 * This means that for the fairly common cases of pair-wise Query operators
389 * and Database objects with one or two subdatabases, we use less space than
390 * std::vector<Xapian::Foo::Internal*> would.
392 template<typename TI>
393 class SmallVectorI : public SmallVector_ {
394 public:
395 typedef std::size_t size_type;
397 typedef TI*const* const_iterator;
399 // Create an empty SmallVectorI.
400 SmallVectorI() : SmallVector_() { }
402 // Create an empty SmallVectorI with n elements reserved.
403 explicit SmallVectorI(size_type n) : SmallVector_(n) { }
405 SmallVectorI(SmallVectorI&& o) noexcept : SmallVector_(o) { }
407 ~SmallVectorI() {
408 clear();
411 const_iterator begin() const {
412 return const_iterator(do_begin());
415 const_iterator end() const {
416 return const_iterator(do_end());
419 void clear() {
420 for (const_iterator i = begin(); i != end(); ++i)
421 if ((*i) && --(*i)->_refs == 0)
422 delete *i;
424 do_clear();
427 void push_back(TI* elt) {
428 // Cast away potential const-ness in TI. We can only try to modify an
429 // element after casting to TI*, so this is const-safe overall.
430 do_push_back(const_cast<void*>(static_cast<const void*>(elt)));
431 if (elt)
432 ++elt->_refs;
435 TI* operator[](size_type idx) const {
436 return begin()[idx];
439 TI* front() const {
440 return *(begin());
443 TI* back() const {
444 return end()[-1];
448 /** Vector of Xapian PIMPL objects.
450 * A notable feature is that if the vector holds <= 2 objects, there's no
451 * extra storage - the two internal pointers are held in the two
452 * pointers which otherwise point to the first and just after the last
453 * element.
455 * This means that for the fairly common cases of pair-wise Query operators
456 * and Database objects with one or two subdatabases, we use less space than
457 * std::vector<Xapian::Foo> would.
459 template<typename T>
460 class SmallVector : public SmallVectorI<typename T::Internal> {
461 typedef SmallVectorI<typename T::Internal> super;
463 public:
464 typedef std::size_t size_type;
466 class const_iterator {
467 typename super::const_iterator ptr;
469 public:
470 const_iterator() : ptr(nullptr) { }
472 explicit const_iterator(typename super::const_iterator ptr_)
473 : ptr(ptr_) { }
475 const_iterator & operator++() { ++ptr; return *this; }
477 const_iterator operator++(int) { return const_iterator(ptr++); }
479 T operator*() const {
480 return T(*ptr);
483 T operator[](size_type idx) const {
484 return T(ptr[idx]);
487 bool operator==(const const_iterator& o) const { return ptr == o.ptr; }
489 bool operator!=(const const_iterator& o) const { return !(*this == o); }
491 const_iterator operator+(int n) { return const_iterator(ptr + n); }
493 const_iterator operator-(int n) { return const_iterator(ptr - n); }
496 // Create an empty SmallVector.
497 SmallVector() { }
499 // Create an empty SmallVector with n elements reserved.
500 explicit SmallVector(size_type n) : super(n) { }
502 const_iterator begin() const {
503 return const_iterator(super::begin());
506 const_iterator end() const {
507 return const_iterator(super::end());
510 using super::push_back;
512 void push_back(const T & elt) {
513 push_back(elt.internal.get());
516 T operator[](size_type idx) const {
517 return begin()[idx];
520 T front() const {
521 return *(begin());
524 T back() const {
525 return end()[-1];
531 #endif // XAPIAN_INCLUDED_SMALLVECTOR_H