This is my custom static vector implementation similar to Boost’s StaticVector. Basically the idea is that the container should have a fixed compile time capacity, never dynamically allocates but should otherwise behave like a std::vector. Please kindly offer any suggestions/improvements to the code.
#include <iostream>
#include <utility>
#include <iterator>
#include <cstddef>
#include <type_traits>
#include <cstdint>
#include <limits>
#include <algorithm>
template<std::size_t Capacity>
using SizeType = typename std::conditional_t<
(Capacity <= std::numeric_limits<uint8_t>::max()), uint8_t,
typename std::conditional_t<
(Capacity <= std::numeric_limits<uint16_t>::max()), uint16_t,
typename std::conditional_t<
(Capacity <= std::numeric_limits<uint32_t>::max()), uint32_t,
uint64_t
>
>
>;
template<typename T, std::size_t Capacity>
class StaticVector {
public:
using value_type = T;
using iterator = T*;
using const_iterator = const T*;
using size_type = SizeType<Capacity>;
static constexpr bool is_raw_storage = !std::is_trivially_constructible<T>::value;
StaticVector() : size_(0) {}
~StaticVector() {
clear();
}
iterator push_back(const T& value) {
if (size_ >= Capacity) {
return end(); // Return end() to indicate failure
}
if constexpr (is_raw_storage) {
new (buffer(size_)) T(value);
} else {
data_[size_] = value;
}
return aligned_buffer(size_++);
}
template<typename... Args>
iterator emplace_back(Args&&... args) {
if (size_ >= Capacity) {
return end(); // Return end() to indicate failure
}
if constexpr (is_raw_storage) {
new (buffer(size_)) T(std::forward<Args>(args)...);
} else {
data_[size_] = T(std::forward<Args>(args)...);
}
return aligned_buffer(size_++);
}
iterator insert(const_iterator pos, const T& value) {
size_type idx = pos - cbegin();
if (idx > size_ || size_ >= Capacity) {
return end(); // Return end() to indicate failure
}
if constexpr (is_raw_storage) {
new (buffer(size_)) T(std::move(*aligned_buffer(size_ - 1)));
std::move_backward(begin() + idx, end() - 1, end());
*aligned_buffer(idx) = value;
} else {
std::move_backward(begin() + idx, end(), end() + 1);
data_[idx] = value;
}
++size_;
return aligned_buffer(idx);
}
iterator erase(const_iterator pos) {
size_type idx = pos - cbegin();
if (idx >= size_) {
return end(); // Return end() to indicate failure
}
if constexpr (is_raw_storage) {
aligned_buffer(idx)->~T();
std::move(begin() + idx + 1, end(), begin() + idx);
} else {
std::move(begin() + idx + 1, end(), begin() + idx);
}
--size_;
return aligned_buffer(idx);
}
void clear() {
if constexpr (is_raw_storage) {
for (size_type i = 0; i < size_; ++i) {
aligned_buffer(i)->~T();
}
}
size_ = 0;
}
iterator begin() {
return aligned_buffer(0);
}
const_iterator begin() const {
return aligned_buffer(0);
}
const_iterator cbegin() const {
return begin();
}
iterator end() {
return aligned_buffer(size_);
}
const_iterator end() const {
return aligned_buffer(size_);
}
const_iterator cend() const {
return end();
}
size_type size() const {
return size_;
}
std::size_t capacity() const {
return Capacity;
}
bool empty() const {
return size_ == 0;
}
T& operator[](size_type index) {
return *aligned_buffer(index);
}
const T& operator[](size_type index) const {
return *aligned_buffer(index);
}
private:
// Helper type trait to choose between array of T or raw bytes
using StorageType = typename std::conditional_t<
std::is_trivially_constructible<T>::value,
T[Capacity],
alignas(alignof(T)) unsigned char[sizeof(T) * Capacity]
>;
// Returns a void* to the next available memory location
void* buffer(size_type index = 0) {
return &data_[index * sizeof(T)];
}
// Returns a T* to the next available memory location
T* aligned_buffer(size_type index = 0) {
if constexpr (is_raw_storage) {
return reinterpret_cast<T*>(buffer(index));
} else {
return &data_[index];
}
}
StorageType data_;
size_type size_;
};
// Example usage
struct NonTrivial {
int a;
NonTrivial(int a) : a(a) { std::cout << "Constructed: " << a << "\n"; }
~NonTrivial() { std::cout << "Destructed: " << a << "\n"; }
};
int main() {
StaticVector<int, 5> trivVec;
trivVec.push_back(1);
trivVec.push_back(2);
trivVec.emplace_back(3);
trivVec.insert(trivVec.cbegin() + 1, 4);
trivVec.erase(trivVec.cbegin() + 2);
std::cout << "Trivial vector contents: ";
for (const auto& elem : trivVec) {
std::cout << elem << " ";
}
std::cout << "\n";
StaticVector<NonTrivial, 5> nonTrivVec;
nonTrivVec.push_back(NonTrivial(1));
nonTrivVec.push_back(NonTrivial(2));
nonTrivVec.emplace_back(3);
nonTrivVec.insert(nonTrivVec.cbegin() + 1, NonTrivial(4));
nonTrivVec.erase(nonTrivVec.cbegin() + 2);
std::cout << "Non-trivial vector contents: ";
for (const auto& elem : nonTrivVec) {
std::cout << elem.a << " ";
}
std::cout << "\n";
return 0;
}
std::vector's methods? \$\endgroup\$