I've wrote a FixedArray to fit seamlessly (function-wise) with the default C++ containers, including working with algorithms/etc. It is a dynamically allocated, but fixed size array.
Code
#pragma once
#include <cstddef>
#include <initializer_list>
#include <algorithm>
#include <limits>
#include <concepts>
#include <iterator>
#include <stdexcept>
namespace Util {
template<typename C, typename T=typename C::value_type>
concept Container = std::input_iterator<typename C::iterator> && std::is_same_v<typename C::value_type,T> && requires(C c,const C cc,size_t i) {
{ c.begin() } -> std::same_as<typename C::iterator>;
{ c.end() } -> std::same_as<typename C::iterator>;
{ cc.begin() } -> std::same_as<typename C::const_iterator>;
{ cc.end() } -> std::same_as<typename C::const_iterator>;
{ c.size() } -> std::same_as<typename C::size_type>;
};
template<typename T, bool _reassignable=false, typename Alloc=std::allocator<T>>
class FixedArray {
public:
using value_type=T;
using allocator_type=Alloc;
using size_type=size_t;
using difference_type=std::ptrdiff_t;
using reference=T&;
using pointer=T*;
using iterator=T*;
using reverse_iterator=std::reverse_iterator<T*>;
using const_reference=const T&;
using const_pointer=const T*;
using const_iterator=const T*;
using const_reverse_iterator=std::reverse_iterator<const T*>;
private:
value_type * _data;
size_type _size;
[[no_unique_address]] Alloc allocator;
void _cleanup() { // destroy all objects, deallocate all memory, leaves container in a valid empty state
if(_data) {
for(size_t i=0;i<_size;i++) {
_data[i].~value_type();
}
allocator.deallocate(_data,_size);
_data=nullptr;
}
_size=0;
}
template<typename C>
void _container_assign(const C &container) { // allocate memory and copy contents from generic container of type C
if(container.size()==0) {
_size=0;
_data=nullptr;
} else {
_size=container.size();
_data=allocator.allocate(_size);
typename C::const_iterator it=container.begin();
for(size_t i=0;i<_size;i++,it++){
new(_data+i) value_type(*it);
}
}
}
public:
FixedArray() : _data(nullptr), _size(0) {
}
FixedArray(std::nullptr_t) : _data(nullptr), _size(0) {
}
FixedArray(std::initializer_list<T> data) requires std::is_copy_constructible_v<value_type> {
_container_assign(data);
}
template<typename U>
FixedArray(std::initializer_list<U> data) requires (!std::is_same_v<T,U>) && requires (U x){ T(x); }
{
_container_assign(data);
}
FixedArray(const FixedArray & other) requires std::is_copy_constructible_v<value_type> {
_container_assign(other);
}
FixedArray(FixedArray && other) : _data(other._data), _size(other._size) {
other._data=nullptr;
other._size=0;
}
template<typename C>
FixedArray(const C& container) requires Container<C,value_type> && std::is_copy_constructible_v<value_type> {
_container_assign(container);
}
template<typename C>
FixedArray(const C& container) requires Container<C> && (!std::is_same_v<T,typename C::value_type>) && requires (typename C::value_type v) { T(v); }
{
_container_assign(container);
}
FixedArray(size_t n) requires std::is_default_constructible_v<value_type> : _data(nullptr), _size(n) {
if(_size>0){
_data=allocator.allocate(_size);
for(size_t i=0;i<_size;i++){
new(_data+i) value_type();
}
}
}
~FixedArray() {
_cleanup();
}
// --- optional reassignment methods ---
void operator=(std::nullptr_t) requires _reassignable {
_cleanup();
}
void clear() requires _reassignable {
_cleanup();
}
FixedArray & operator=(std::initializer_list<T> data) requires _reassignable {
_cleanup();
_container_assign(data);
return *this;
}
template<typename U>
FixedArray & operator=(std::initializer_list<U> data) requires _reassignable && requires (U x){ T(x); }
{
_cleanup();
_container_assign(data);
return *this;
}
FixedArray & operator=(const FixedArray & other) requires _reassignable {
_cleanup();
_container_assign(other);
return *this;
}
FixedArray & operator=(FixedArray && other) requires _reassignable {
_cleanup();
_data=other._data;
_size=other._size;
other._data=nullptr;
other._size=0;
return *this;
}
template<typename C>
FixedArray & operator=(const C& container) requires _reassignable && Container<C,value_type> {
_cleanup();
_container_assign(container);
return *this;
}
template<typename C>
FixedArray & operator=(const C& container) requires _reassignable && Container<C> && (!std::is_same_v<T,typename C::value_type>) && requires (typename C::value_type v) { T(v); }
{
_cleanup();
_container_assign(container);
return *this;
}
void swap(FixedArray & other) requires _reassignable {
value_type * temp_data=_data;
size_t temp_size=_size;
_data=other._data;
_size=other._size;
other._data=temp_data;
other._size=temp_size;
}
static void swap(FixedArray & lhs,FixedArray & rhs) requires _reassignable {
lhs.swap(rhs);
}
// --- container boilerplate after this ---
template<typename C>
bool operator==(const C& container) const {
return _size==container.size()&&std::equal(container.begin(),container.end(),begin(),end());
}
template<typename C>
bool operator!=(const C& container) const {
return _size!=container.size()||!std::equal(container.begin(),container.end(),begin(),end());
}
size_type size() const noexcept {
return _size;
}
static size_type max_size() noexcept {
return (std::numeric_limits<size_type>::max()/sizeof(value_type))-1;
}
size_type empty() const noexcept {
return _size==0;
}
iterator begin() noexcept {
return _data;
}
iterator end() noexcept {
return _data+_size;
}
const_iterator begin() const noexcept {
return _data;
}
const_iterator end() const noexcept {
return _data+_size;
}
const_iterator cbegin() const noexcept {
return _data;
}
const_iterator cend() const noexcept {
return _data+_size;
}
reverse_iterator rbegin() noexcept {
return std::make_reverse_iterator(end());
}
reverse_iterator rend() noexcept {
return std::make_reverse_iterator(begin());
}
const_reverse_iterator rbegin() const noexcept {
return std::make_reverse_iterator(end());
}
const_reverse_iterator rend() const noexcept {
return std::make_reverse_iterator(begin());
}
const_reverse_iterator crbegin() const noexcept {
return std::make_reverse_iterator(end());
}
const_reverse_iterator crend() const noexcept {
return std::make_reverse_iterator(begin());
}
pointer data() noexcept {
return _data;
}
const_pointer data() const noexcept {
return _data;
}
reference front() noexcept {
return _data[0];
}
const_reference front() const noexcept {
return _data[0];
}
reference back() noexcept {
return _data[_size-1];
}
const_reference back() const noexcept {
return _data[_size-1];
}
reference operator[](size_t i) noexcept {
return _data[i];
}
const_reference operator[](size_t i) const noexcept {
return _data[i];
}
reference at(size_t i) {
if(i>=_size) throw std::out_of_range("Index "+std::to_string(i)+" is out of range for FixedArray of size "+std::to_string(_size));
return _data[i];
}
const_reference at(size_t i) const {
if(i>=_size) throw std::out_of_range("Index "+std::to_string(i)+" is out of range for FixedArray of size "+std::to_string(_size));
return _data[i];
}
};
}
After testing it, it seems to work without any problems, but a second pair of eyes could help find anything i might have overlooked.
std::Array
? \$\endgroup\$ – Edward Dec 27 '20 at 21:23std::vector
that you just never resize. Why would someone want to use this class overstd::vector
? The latter also allows construction from any other container-like object, given two iterators. \$\endgroup\$ – G. Sliepen Dec 27 '20 at 21:35(n --> 0]
to mimik this behavior. \$\endgroup\$ – Martin York Dec 28 '20 at 1:23