https://github.com/gharveymn/small_vector
A fully featured single header library implementing a vector container with a small buffer optimization.
https://github.com/gharveymn/small_vector
concepts cpp single-header-library small-vector
Last synced: 20 days ago
JSON representation
A fully featured single header library implementing a vector container with a small buffer optimization.
- Host: GitHub
- URL: https://github.com/gharveymn/small_vector
- Owner: gharveymn
- License: mit
- Created: 2021-01-04T09:30:48.000Z (about 5 years ago)
- Default Branch: main
- Last Pushed: 2025-04-27T01:40:45.000Z (11 months ago)
- Last Synced: 2025-04-27T02:28:36.782Z (11 months ago)
- Topics: concepts, cpp, single-header-library, small-vector
- Language: C++
- Homepage:
- Size: 586 KB
- Stars: 59
- Watchers: 3
- Forks: 5
- Open Issues: 8
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# gch::small_vector
This is a vector container implementation with a small buffer optimization. It doesn't have any
dependencies unlike the `boost::container::small_vector` and `llvm::SmallVector` implementations
and may be used as a drop-in header (along with the license).
Performance is about on par with the [other implementations](#Other-Implementations), but with
stricter conformance to the named requirements as specified in the standard. Consequently, this
implementation is close to API compatible with `std::vector`, and in many cases can be used as a
drop-in replacement.
This library is compatible with C++11 and up, with `constexpr` and `concept` support for C++20.
## Technical Overview
```c++
template >::value,
typename Allocator = std::allocator>
class small_vector;
```
A `small_vector` is a contiguous sequence container with a certain amount of dedicated storage on
the stack. When this storage is filled up, it switches to allocating on the heap.
A `small_vector` supports insertion and erasure operations in 𝓞(1) the end, and 𝓞(n) in the middle.
It also meets the requirements of *Container*, *AllocatorAwareContainer*, *SequenceContainer*,
*ContiguousContainer*, and *ReversibleContainer*.
Template arguments may be used to define the type of stored elements, the number of elements to be
stored on the stack, and the type of allocator to be used.
When compiling with C++20 support, `small_vector` may be used in `constexpr` expressions.
A `small_vector` may be instantiated with an incomplete type `T`, but only if `InlineCapacity` is
equal to `0`.
When compiling with support for `concept`s with complete type `T`, instantiation of a `small_vector`
requires that `Allocator` meet the *Allocator* named requirements. Member functions are also
constrained by various `concept`s when `T` is complete.
The default argument for `InlineCapacity` is computed to be the number of elements needed to size
the object at 64 bytes. If the size of `T` makes this impossible to do with a non-zero number of
elements, the value of `InlineCapacity` is set to `1`.
## Usage
You have a few options to import this library into your project. It is a single-header library,
so you should be able to use it without too much fuss.
### As a Drop-In Header
Just to drop `source/include/gch/small_vector.hpp` and `docs/LICENSE` into your project.
### As a Git Submodule
You can add the project as a submodule with
```commandline
git submodule add -b main git@github.com:gharveymn/small_vector.git external/small_vector
```
and then add it as a subdirectory to your project by adding something like
```cmake
add_subdirectory (external/small_vector)
target_link_libraries ( PRIVATE gch::small_vector)
```
to `CMakeLists.txt`.
### As a System Library
You can install the library with (Unix-specific, remove `sudo` on Windows)
```commandline
git clone git@github.com:gharveymn/small_vector.git
cmake -D GCH_SMALL_VECTOR_ENABLE_TESTS=OFF -B small_vector/build -S small_vector
sudo cmake --install small_vector/build
```
and then link it to your project by adding
```cmake
find_package (small_vector REQUIRED)
target_link_libraries ( PRIVATE gch::small_vector)
```
to `CMakeLists.txt`.
## Q&A
### Can I specify the `size_type` like with `folly::small_vector`?
Not directly no, but `gch::small_vector::size_type` is derived from `std::allocator_traits`,
so you can just write a wrapper around whatever allocator you're using to modify it. This can be
done with something like
```c++
template
struct tiny_allocator
: std::allocator
{
using size_type = std::uint16_t;
using std::allocator::allocator;
// You don't need either of the following when compiling for C++20.
template
struct rebind { using other = tiny_allocator; };
void
max_size (void) = delete;
};
int
main (void)
{
small_vector vs;
std::cout << "std::allocator:" << '\n'
<< " sizeof (vs): " << sizeof (vs) << '\n'
<< " Inline capacity: " << vs.inline_capacity () << '\n'
<< " Maximum size: " << vs.max_size () << "\n\n";
small_vector>, tiny_allocator> vt;
std::cout << "tiny_allocator:" << '\n'
<< " sizeof (vt): " << sizeof (vt) << '\n'
<< " Inline capacity: " << vt.inline_capacity () << '\n'
<< " Maximum size: " << vt.max_size () << std::endl;
}
```
Output:
```text
std::allocator:
sizeof (vs): 64
Inline capacity: 10
Maximum size: 4611686018427387903
tiny_allocator:
sizeof (vt): 64
Inline capacity: 12
Maximum size: 16383
```
### How can I use this with my STL container template templates?
You can create a homogeneous template wrapper with something like
```c++
template ,
typename InlineCapacityType =
std::integral_constant::value>>
using small_vector_ht = small_vector;
template class VectorT>
void
f (void)
{
VectorT x;
VectorT y;
/* ... */
}
void
g (void)
{
f ();
f ();
}
```
I don't include this in the header because too much choice can often cause confusion about how
things are supposed to be used.
### How do I use this in a `constexpr`?
In C++20, just as with `std::vector`, you cannot create a `constexpr` object of type
`small_vector`. However, you can use it *inside* a `constexpr`. That is,
```c++
// Allowed.
constexpr
int
f (void)
{
small_vector v { 1, 2, 3 };
return std::accumulate (v.begin (), v.end (), 0);
}
// Not allowed.
// constexpr small_vector w { };
```
### How do I disable the `concept`s?
You can define the preprocessor directive `GCH_DISABLE_CONCEPTS` with your compiler. In CMake:
```cmake
target_compile_definitions ( PRIVATE GCH_DISABLE_CONCEPTS)
```
These are a bit experimental at the moment, so if something is indeed incorrect please feel free to send
me a note to fix it.
### How can I enable the pretty-printer for GDB?
Assuming you have installed the library, you can add the following to either `$HOME/.gdbinit` or
`$(pwd)/.gdbinit`.
```gdb
python
import sys
sys.path.append("/usr/local/share/gch/python")
from gch.gdb.prettyprinters import small_vector
end
```
If you installed the library to another location, simply adjust the path as needed.
As a side-note, I haven't found a way to automatically install pretty printers for GDB, so if someone knows how to
do so, please let me know.
### Why do I get bad performance compared to other implementations when using this with a throwing move constructor?
This implementation provides the same strong exception guarantees as the STL vector. So, in some
cases copies will be made instead of moves when relocating elements if the move constructor of the
value type may throw.
You can disable the strong exception guarantees by defining
`GCH_NO_STRONG_EXCEPTION_GUARANTEES` before including the header.
### Why isn't my `small_vector` empty after moving it?
In cases where the `small_vector` is inlined, we leverage the language of the standard which states that objects which
have been moved from are placed in a "valid but unspecified state". So, while the data inside the objects of the
`small_vector` will have been moved, they will not necessarily have been destroyed. This makes it so we can get a bit
better performance if we are immediately moving new data into the `small_vector` which had been moved from.
The following shows the expected behavior.
```c++
gch::small_vector v1 { "hi", "howdy", "hello" };
auto v2 = std::move (v1);
assert (v2.size () == 3);
assert (v1.size () == 3);
for (auto& s : v1)
assert (s.empty());
```
## Brief
In the interest of succinctness, this brief is prepared with declaration decorations compatible
with C++20. The `constexpr` and `concept` features will not be available for other versions of the
standard.
Also note that I've omitted the namespacing and template arguments in the `concept`s used in
most of the `requires` statements. Those arguments involve `value_type`, `small_vector`, and `allocator_type`, in the
obvious fashion.
```c++
namespace gch
{
namespace concepts
{
template concept MoveAssignable;
template concept CopyAssignable;
template concept Swappable;
template
concept EmplaceConstructible;
template concept DefaultInsertable;
template concept MoveInsertable;
template concept CopyInsertable;
template concept Erasable;
template concept AllocatorFor;
template concept Allocator;
}
// A class used to calculate the default number of elements in inline storage using a heuristic.
template
requires concepts::Allocator
struct default_buffer_size;
template
inline constexpr
unsigned
default_buffer_size_v = default_buffer_size::value;
// A contiguous iterator (just a pointer wrapper).
template
class small_vector_iterator;
template >,
typename Allocator = std::allocator>
requires concepts::AllocatorFor
class small_vector
{
public:
using value_type = T;
using allocator_type = Allocator;
using size_type = typename std::allocator_traits::size_type;
using difference_type = /* min { signed size_type, alloc_traits::difference_type } */;
using reference = value_type&;
using const_reference = const value_type&;
using pointer = typename std::allocator_traits::pointer;
using const_pointer = typename std::allocator_traits::const_pointer;
using iterator = small_vector_iterator;
using const_iterator = small_vector_iterator;
using reverse_iterator = std::reverse_iterator;
using const_reverse_iterator = std::reverse_iterator;
/* construction */
constexpr
small_vector (void)
noexcept (noexcept (allocator_type ()))
requires concepts::DefaultConstructible;
constexpr
small_vector (const small_vector& other)
requires CopyInsertable && CopyAssignable;
constexpr
small_vector (small_vector&& other)
noexcept (std::is_nothrow_move_constructible::value || 0 == InlineCapacity)
requires MoveInsertable;
constexpr explicit
small_vector (const allocator_type& alloc) noexcept;
constexpr
small_vector (const small_vector& other, const allocator_type& alloc)
requires CopyInsertable;
constexpr
small_vector (small_vector&& other, const allocator_type& alloc)
requires MoveInsertable;
constexpr explicit
small_vector (size_type count, const allocator_type& alloc = allocator_type ())
requires DefaultInsertable;
constexpr
small_vector (size_type count, const_reference value,
const allocator_type& alloc = allocator_type ())
requires CopyInsertable;
template
requires std::invocable
&& EmplaceConstructible>
GCH_CPP20_CONSTEXPR
small_vector (size_type count, Generator g, const allocator_type& alloc = allocator_type ());
template
requires EmplaceConstructible>
&& (std::forward_iterator || MoveInsertable)
constexpr
small_vector (InputIt first, InputIt last, const allocator_type& alloc = allocator_type ());
constexpr
small_vector (std::initializer_list init,
const allocator_type& alloc = allocator_type ())
requires EmplaceConstructible;
template
requires CopyInsertable && CopyAssignable
constexpr explicit
small_vector (const small_vector& other);
template
requires (LessI < InlineCapacity) && MoveInsertable
constexpr explicit
small_vector (small_vector&& other)
noexcept (std::is_nothrow_move_constructible::value);
template
requires (InlineCapacity < GreaterI) && MoveInsertable
constexpr explicit
small_vector (small_vector&& other);
template
requires CopyInsertable
constexpr
small_vector (const small_vector& other, const allocator_type& alloc);
template
requires MoveInsertable
constexpr
small_vector (small_vector&& other, const allocator_type& alloc);
/* destruction */
constexpr
~small_vector (void)
requires Erasable;
/* assignment */
constexpr
small_vector&
operator= (const small_vector& other)
requires CopyInsertable && CopyAssignable;
constexpr
small_vector&
operator= (small_vector&& other)
noexcept ( ( std::is_same, Allocator>::value
|| std::allocator_traits::propagate_on_container_move_assignment::value
|| std::allocator_traits::is_always_equal::value
)
&& ( ( std::is_nothrow_move_assignable::value
&& std::is_nothrow_move_constructible::value
)
|| InlineCapacity == 0
)
)
requires MoveInsertable && MoveAssignable;
constexpr
small_vector&
operator= (std::initializer_list ilist)
requires CopyInsertable && CopyAssignable;
constexpr
void
assign (size_type count, const_reference value)
requires CopyInsertable && CopyAssignable;
template
requires EmplaceConstructible>
&& (std::forward_iterator || MoveInsertable)
constexpr
void
assign (InputIt first, InputIt last);
constexpr
void
assign (std::initializer_list ilist)
requires EmplaceConstructible;
template
requires CopyInsertable && CopyAssignable
constexpr
void
assign (const small_vector& other);
constexpr
void
assign (small_vector&& other)
noexcept ( ( std::is_same, Allocator>::value
|| std::allocator_traits::propagate_on_container_move_assignment::value
|| std::allocator_traits::is_always_equal::value
)
&& ( ( std::is_nothrow_move_assignable::value
&& std::is_nothrow_move_constructible::value
)
|| InlineCapacity == 0
)
)
requires MoveInsertable && MoveAssignable;
template
requires (LessI < InlineCapacity) && MoveInsertable && MoveAssignable
constexpr
void
assign (small_vector&& other)
noexcept ( ( std::is_same, Allocator>::value
|| std::allocator_traits::propagate_on_container_move_assignment::value
|| std::allocator_traits::is_always_equal::value
)
&& std::is_nothrow_move_assignable::value
&& std::is_nothrow_move_constructible::value
);
template
requires (InlineCapacity < GreaterI) && MoveInsertable && MoveAssignable
constexpr
void
assign (small_vector&& other);
constexpr
void
swap (small_vector& other)
noexcept ( ( std::is_same, Allocator>::value
|| std::allocator_traits::propagate_on_container_swap::value
|| std::allocator_traits::is_always_equal::value
)
&& ( ( std::is_nothrow_move_constructible::value
&& std::is_nothrow_move_assignable::value
&& std::is_nothrow_swappable::value
)
|| InlineCapacity == 0
)
)
requires (MoveInsertable && MoveAssignable && Swappable)
|| ( ( std::is_same, Allocator>::value
|| std::allocator_traits::propagate_on_container_swap::value
|| std::allocator_traits::is_always_equal::value
)
&& InlineCapacity == 0
)
template
constexpr
void
swap (small_vector& other)
requires (MoveInsertable && MoveAssignable && Swappable)
|| ( ( std::is_same, Allocator>::value
|| std::allocator_traits::propagate_on_container_swap::value
|| std::allocator_traits::is_always_equal::value
)
)
/* iteration */
constexpr iterator begin (void) noexcept;
constexpr const_iterator begin (void) const noexcept;
constexpr const_iterator cbegin (void) const noexcept;
constexpr iterator end (void) noexcept;
constexpr const_iterator end (void) const noexcept;
constexpr const_iterator cend (void) const noexcept;
constexpr reverse_iterator rbegin (void) noexcept;
constexpr const_reverse_iterator rbegin (void) const noexcept;
constexpr const_reverse_iterator crbegin (void) const noexcept;
constexpr reverse_iterator rend (void) noexcept;
constexpr const_reverse_iterator rend (void) const noexcept;
constexpr const_reverse_iterator crend (void) const noexcept;
/* access */
constexpr reference at (size_type pos);
constexpr const_reference at (size_type pos) const;
constexpr reference operator[] (size_type pos);
constexpr const_reference operator[] (size_type pos) const;
constexpr reference front (void);
constexpr const_reference front (void) const;
constexpr reference back (void);
constexpr const_reference back (void) const;
constexpr pointer data (void) noexcept;
constexpr const_pointer data (void) const noexcept;
/* state information */
[[nodiscard]]
constexpr bool empty (void) const noexcept;
constexpr size_type size (void) const noexcept;
constexpr size_type max_size (void) const noexcept;
constexpr size_type capacity (void) const noexcept;
constexpr allocator_type get_allocator (void) const noexcept;
/* insertion */
constexpr
iterator
insert (const_iterator pos, const_reference value)
requires CopyInsertable && CopyAssignable;
constexpr
iterator
insert (const_iterator pos, value_type&& value)
requires MoveInsertable && MoveAssignable;
constexpr
iterator
insert (const_iterator pos, size_type count, const_reference value)
requires CopyInsertable && CopyAssignable;
template
requires EmplaceConstructible>
&& MoveInsertable
&& MoveAssignable
constexpr
iterator
insert (const_iterator pos, InputIt first, InputIt last);
constexpr
iterator
insert (const_iterator pos, std::initializer_list ilist)
requires EmplaceConstructible
&& MoveInsertable
&& MoveAssignable;
template
requires EmplaceConstructible
&& MoveInsertable
&& MoveAssignable
constexpr
iterator
emplace (const_iterator pos, Args&&... args);
constexpr
iterator
erase (const_iterator pos)
requires MoveAssignable && Erasable;
constexpr
iterator
erase (const_iterator first, const_iterator last)
requires MoveAssignable && Erasable;
constexpr
void
push_back (const_reference value)
requires CopyInsertable;
constexpr
void
push_back (value_type&& value)
requires MoveInsertable;
template
requires EmplaceConstructible && MoveInsertable
constexpr
reference
emplace_back (Args&&... args);
constexpr
void
pop_back (void)
requires Erasable;
/* global state modification */
constexpr
void
reserve (size_type new_cap)
requires MoveInsertable;
constexpr
void
shrink_to_fit (void)
requires MoveInsertable;
constexpr
void
clear (void) noexcept
requires Erasable;
constexpr
void
resize (size_type count)
requires MoveInsertable && DefaultInsertable;
constexpr
void
resize (size_type count, const_reference value)
requires CopyInsertable;
/* non-standard */
[[nodiscard]] constexpr bool inlined (void) const noexcept;
[[nodiscard]] constexpr bool inlinable (void) const noexcept;
[[nodiscard]] constexpr size_type inline_capacity (void) const noexcept;
template
requires EmplaceConstructible>
&& MoveInsertable
constexpr
small_vector&
append (InputIt first, InputIt last);
constexpr
small_vector&
append (std::initializer_list ilist)
requires EmplaceConstructible
&& MoveInsertable;
template
constexpr
small_vector&
append (const small_vector& other)
requires CopyInsertable;
template
constexpr
small_vector&
append (small_vector&& other)
requires MoveInsertable;
};
/* non-member functions */
template
constexpr
bool
operator== (const small_vector& lhs,
const small_vector& rhs);
template
constexpr
auto
operator<=> (const small_vector& lhs,
const small_vector& rhs);
/* insert other comparison boilerplate here if not using C++20 */
template
constexpr
void
swap (small_vector& lhs,
small_vector& rhs)
noexcept (noexcept (lhs.swap (rhs)))
requires MoveInsertable && Swappable;
template
constexpr
void
swap (small_vector& lhs,
small_vector& rhs)
requires MoveInsertable && Swappable;
template
constexpr
typename small_vector::size_type
erase (small_vector& c, const U& value);
template
constexpr
typename small_vector::size_type
erase_if (small_vector& c, Pred pred);
template
constexpr
auto
begin (small_vector& v) noexcept
-> decltype (v.begin ());
template
constexpr
auto
begin (const small_vector& v) noexcept
-> decltype (v.begin ());
template
constexpr
auto
cbegin (const small_vector& v) noexcept
-> decltype (begin (v));
template
constexpr
auto
end (small_vector& v) noexcept
-> decltype (v.end ());
template
constexpr
auto
end (const small_vector& v) noexcept
-> decltype (v.end ());
template
constexpr
auto
cend (const small_vector& v) noexcept
-> decltype (end (v));
template
constexpr
auto
rbegin (small_vector& v) noexcept
-> decltype (rbegin (v));
template
constexpr
auto
rbegin (const small_vector& v) noexcept
-> decltype (rbegin (v));
template
constexpr
auto
crbegin (const small_vector& v) noexcept
-> decltype (rbegin (v));
template
constexpr
auto
rend (small_vector& v) noexcept
-> decltype (v.rend ());
template
constexpr
auto
rend (const small_vector& v) noexcept
-> decltype (v.rend ());
template
constexpr
auto
crend (const small_vector& v) noexcept
-> decltype (rend (v));
template
constexpr
auto
size (const small_vector& v) noexcept
-> decltype (v.size ());
template
constexpr
auto
ssize (const small_vector& v) noexcept
-> std::common_type_t>;
template
[[nodiscard]] constexpr
auto
empty (const small_vector& v) noexcept
-> decltype (v.empty ())
template
constexpr
auto
data (small_vector& v) noexcept
-> decltype (v.data ())
template
constexpr
auto
data (const small_vector& v) noexcept
-> decltype (v.data ())
template ::value_type>>,
typename Allocator = std::allocator::value_type>>
small_vector (InputIt, InputIt, Allocator = Allocator ())
-> small_vector::value_type, InlineCapacity, Allocator>;
}
```
## Other Implementations
- [boost::container::small_vector](https://www.boost.org/doc/libs/1_60_0/doc/html/boost/container/small_vector.html)
- [llvm::SmallVector](https://llvm.org/doxygen/classllvm_1_1SmallVector.html)
- [folly::small_vector](https://github.com/facebook/folly/blob/main/folly/docs/small_vector.md)
## License
This project may be modified and distributed under the terms of the MIT license. See the [LICENSE](LICENSE)
file for details.