Is there an std container for C-style arrays with variable size?
For example, I have the following code
int size = 5; // This is not a constant in general
int *my_array = SpecialAllocationFunction(size);
I want to be able to access this array with a C++, std-style container. Something that has iterators and functions like: size, begin, end, ...
I know that I can use std::array if my_array has a constant size. I also can write one myself, but I feel there bound to be something ready-made.
Using a custom allocator, it is possible to build a vector with a size known only at run time (so no std::array possible), wrapping an existing array. It is even possible to keep pre-existing values by overriding the special construct method(*).
Here is a possible implementation:
/**
* a pseudo allocator which receives in constructor an existing array
* of a known size, and will return it provided the required size
* is less than the declared one. If keep is true in contructor,
* nothing is done at object construction time: original values are
* preserved
* at deallocation time, nothing will happen
*/
template <class T>
class SpecialAllocator {
T * addr;
size_t sz;
bool keep;
public:
typedef T value_type;
SpecialAllocator(T * addr, size_t sz, bool keep):
addr(addr), sz(sz), keep(keep) {}
size_t max_size() {
return sz;
}
T* allocate(size_t n, const void* hint=0) {
if (n > sz) throw std::bad_alloc(); // throws a bad_alloc...
return addr;
}
void deallocate(T* p, size_t n) {}
template <class U, class... Args>
void construct(U* p, Args&&... args) {
if (! keep) {
::new((void *)p) U(std::forward<Args>(args)...);
}
}
template <class U>
void destroy(U* p) {
if (! keep) {
p->~U(); // do not destroy what we have not constructed...
}
}
};
It can then be used that way:
int size = 5; // This is not a constant in general
int *my_array = SpecialAllocationFunction(size);
SpecialAllocator<int> alloc(my_array, size);
std::vector<int, SpecialAllocator<int> > vec(size, alloc);
From that point, vec will be a true std::vector wrapping my_array.
Here is a simple code as demo:
int main(){
int arr[5] = { 5, 4, 3, 2, 1 };
SpecialAllocator<int> alloc(arr, 5, true); // original values will be preserved
std::vector<int, SpecialAllocator<int> > vec(5, alloc);
for(auto it= vec.begin(); it != vec.end(); it++) {
std::cout << *it << " ";
}
std::cout << std::endl;
try {
vec.push_back(8);
}
catch (std::bad_alloc& a) {
std::cout << "allocation error" << std::endl;
}
return 0;
}
It will successfully output:
5 4 3 2 1
allocation error
(*) BEWARE: Construction/destruction may be involved in different places: push_back, emplace_back, etc. Really think twice about your real use case before using no-op construct and destroy methods.
As #NathanOliver and #utnapistim said in the comments, gsl::span works. Since I don't want to include this library I ended up writing a "trivial wrapper" myself. Included below for others looking for an answer (and my future self)
template<class T>
class span {
public:
inline span() : _data(0), _size(0) {}
inline span(T* d, size_t s) : _data(d), _size(s) {}
inline T& operator[](size_t index) { return _data[index]; }
inline const T& operator[](size_t index) const { return _data[index];}
inline size_t size() const { return _size; };
inline T* begin() { return _data; }
inline const T* begin() const { return _data; }
inline T* end() { return _data+_size; }
inline const T* end() const { return _data+_size; }
protected:
T* _data;
size_t _size;
};
Related
I have come across many occasions where I want to have an item which is selected inside a vector, for this I have written the template class:
// a vector wrapper which allows a specific item to be currently selected
template<typename T>
class VectorSelectable
{
public:
VectorSelectable() {};
VectorSelectable(std::initializer_list<T> items) : m_Items(items) {};
void Add(const T& v) { m_Items.push_back(v); m_CurrentIndex = m_Items.size()-1; } // lvalue & refs
void Add(T&& v) { m_Items.push_back(std::move(v)); m_CurrentIndex = m_Items.size()-1; } // rvalue
void Remove(size_t index) {
assert(index < m_Items.size());
m_Items.erase(m_Items.begin() + index);
if(m_CurrentIndex != -1 && (int)index <= m_CurrentIndex)
m_CurrentIndex--;
}
void RemoveCurrent() { assert(m_CurrentIndex > -1 && m_CurrentIndex < (int)m_Items.size()); Remove(m_CurrentIndex); }
T& CurrentItem() { assert(m_CurrentIndex > -1 && m_CurrentIndex < (int)m_Items.size()); return m_Items[m_CurrentIndex]; }
T& operator [](size_t index) { assert(index < Size()); return m_Items[index]; }
// moves value of n_next onto n, and n_new onto n
void ItemSwap(size_t n, size_t n_Next) {
assert(n < m_Items.size());
assert(n_Next < m_Items.size());
T itemBuf = std::move(m_Items[n]);
m_Items[n] = m_Items[n_Next];
m_Items[n_Next] = std::move(itemBuf);
}
size_t Size() { return m_Items.size(); }
const std::vector<T>& Data() { return m_Items; }
std::vector<T>* DataPtr() { return &m_Items; }
T* ItemPtr(size_t index) { assert(index < m_Items.size()); return &m_Items[index]; }
void SetCurrentIndex(int index) { assert(index >= -1 && index < (int)m_Items.size()); m_CurrentIndex = index; }
int& CurrentIndex() { return m_CurrentIndex; }
bool HasItemSelected() { return m_CurrentIndex != -1; }
private:
std::vector<T> m_Items;
int m_CurrentIndex = -1;
};
I am also coming across many scenarios where I want a vector of unique_ptrs (generally for polymorphic classes), this looks like this:
template<typename T>
class Vector_UniquePtrs
{
public:
// Adds an Item (and returns a raw ptr to it)
// usage: v.Add() ... (equivelent to v.Add<base_class>())
template<typename... Args>
T* Add(Args... args) {
return Add<T>(args...);
}
// Adds a Polymorphic Item (and returns a raw ptr to it)
// usage: v.Add<sub_class>()
template<typename T2, typename... Args>
T* Add(Args... args) {
m_Items.push_back(std::unique_ptr<T>(new T2(args...)));
return m_Items.back().get();
}
// Remove Item
void Remove(size_t index) {
assert(index < m_Items.size());
m_Items.erase(m_Items.begin() + index);
}
T* operator [](size_t index) { assert(index < Size()); return m_Items[index].get(); }
size_t Size() { return m_Items.size(); }
private:
std::vector<std::unique_ptr<T>> m_Items;
};
My question is:
How can I handle a combination of these 2 class types (e.g. VectorSelectable<unique_ptr>) as one returns ptrs, the other returns references, is the only option to write an entirely new class?
You mainly need to put the std::vector<std::unique_ptr<T>> in VectorSelectable and hide all the pointer stuff from the interface. With a few small changes to your class, it could look like this:
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <utility>
#include <vector>
template <typename T>
class VectorPtrSelectable {
public:
VectorPtrSelectable() = default;
VectorPtrSelectable(std::initializer_list<T> items) :
m_CurrentIndex(items.size() - 1)
{
m_Items.reserve(items.size());
// fill `m_Items` from the initializer list ...
std::transform(items.begin(), items.end(), std::back_inserter(m_Items),
[](const T& item) {
// ... by creating a unique_ptr from each element (transformation)
return std::make_unique<T>(item);
});
};
template <class U, class... Args>
T& Add(Args&&... args) {
// make `Add` forward to `make_unique`
m_Items.emplace_back(std::make_unique<U>(std::forward<Args>(args)...));
m_CurrentIndex = m_Items.size() - 1;
// and return a reference instead
return *m_Items.back();
}
template <class... Args>
T& Add(Args&&... args) {
// forward to Add<U>
return Add<T>(std::forward<Args>(args)...);
}
void Remove(size_t index) {
m_Items.erase(std::next(m_Items.begin(), index));
if (m_CurrentIndex != static_cast<size_t>(-1) && index <= m_CurrentIndex)
m_CurrentIndex--;
}
T& operator[](size_t index) { return *m_Items[index]; }
const T& operator[](size_t index) const { return *m_Items[index]; }
T& CurrentItem() { return *m_Items[m_CurrentIndex]; }
const T& CurrentItem() const { return *m_Items[m_CurrentIndex]; }
void SetCurrentIndex(size_t index) { m_CurrentIndex = index; }
void RemoveCurrent() { Remove(m_CurrentIndex); }
bool HasItemSelected() { return m_CurrentIndex != static_cast<size_t>(-1); }
void ItemSwap(size_t n, size_t n_Next) {
// simplified swapping:
std::swap(m_Items[n], m_Items[n_Next]);
}
// make functions that does not change your instance const qualified:
size_t CurrentIndex() const { return m_CurrentIndex; }
size_t Size() const { return m_Items.size(); }
private:
std::vector<std::unique_ptr<T>> m_Items;
size_t m_CurrentIndex = static_cast<size_t>(-1); // size_t for the index
};
Example usage:
#include <iostream>
#include <string>
int main() {
VectorPtrSelectable<std::string> vs{"World", "Hello"};
std::cout << vs.CurrentItem() << '\n';
vs.ItemSwap(0, 1);
std::cout << vs.CurrentItem() << '\n';
vs.RemoveCurrent();
std::cout << vs.CurrentItem() << '\n';
std::cout << vs.Add("Add and get a reference") << '\n';
}
Output:
Hello
World
Hello
Add and get a reference
I made m_CurrentIndex a size_t because that's idiomatic but if you'd like to keep it as an int, that's fine too.
std::next(m_Items.begin(), index) will do the same as m_Items.begin() + index, but in cases where the iterator returned by m_Items.begin() is a plain pointer, using std::next avoids potential warnings about using pointer arithmetic.
Returning a reference instead of a pointer to the added element makes no difference other than making the interface more idiomatic. It's simply what a user of the class is likely to expect. Returning a pointer also opens up questions like "can it return nullptr?" etc.
The added const qualified functions makes those functions usable in const contexts too.
template<class T>
void foo(const VectorPtrSelectable<T>& vps) { // note: const&
if(vps.Size() > 0) {
std::cout << "the first element is " << vps[0] << '\n';
std::cout << "the current element is " << vps.CurrentItem() << '\n';
}
}
None of the three member functions used above could be used without the const qualified overloads.
I am running into this error...
...with this minimal executable code to reproduce the error and ...
#include <iostream>
class arrayClass
{
private:
int _elements;
int _array[];
public:
arrayClass()
: _elements(32)
{
//If i is smaller than 2 the "Stack Smashing Detected" error shows up, why is that?
//If i is 2 or more, no error appears
//(f.e. int i = 0 or int i = 1 doesn't work, int i = 2 or higher works - why is that?)
for(int i = 0; i < _elements; ++i){
_array[i] = 0;
}
}
int get(int index){
return _array[index];
}
};
int main()
{
arrayClass arr;
std::cout << arr.get(2) << std::endl;
return 0;
}
...it doesn't matter if I initiate _elements with the initialization list or at with the
attribute itself with f.e.32 or whatever number.
If I pass the int value to construct the arrayClass(int) in addition to the arrayClass() constructor, the error doesn't shows up.
If I construct the arrayClas(int) with a value alone, it can also just be used with the 2nd slot upwards.
So my question is:
Why couldn't I initiate the 1st and 2nd array slot of a default array[]?
Or the other way around, why is it possible to assign an empty array[] to a class without a value and not f.e. _array[32] without an error but the one with assigning array[0] = 0; and array[1] = 0; ?
(And yes, I am aware of vectors, I need to use arrays for various reasons)
Because you never allocate memory for the array to begin with, everything is undefined behavior. I don't even know what int _array[] evaluates to without a size specifier. I'll look that up later.
Change your construct code to "new" the array. And have a destructor to delete it.
class arrayClass
{
private:
int _elements;
int* _array;
public:
arrayClass()
: _elements(32)
{
_array = new int[_elements];
memset(_array, '\0', sizeof(array[0])*_elements);
}
int get(int index){
return _array[index];
}
~arrayClass()
{
delete [] _array;
}
};
Or if you can have a fixed number of elements, explictly size the array when it's declared:
class arrayClass
{
private:
int _array[32];
public:
arrayClass()
: _array() // this will zero-init the array
{
}
int get(int index){
return _array[index];
}
};
int _array[]; is a flexible array and isn't allowed in standard C++. It does not allocate memory so when you access any element in the array, you have undefined behavior.
I am aware of vectors, I need to use arrays for various reasons
In reality there are very few valid reasons so I expect the various reasons you mention to be artificial. If you need to pass the data to a function, like void func(int*, size_t elements);, you can still use a std::vector<int>. Just pass its data() and size() as arguments.
In C++ you should typically use a std::vector<int> for cases like this.
Example:
#include <iostream>
#include <vector>
class arrayClass
{
private:
std::vector<int> _array;
public:
arrayClass(size_t s = 32)
: _array(s)
{}
size_t size() const {
return _array.size();
}
int get(size_t index) const {
return _array[index];
}
};
int main()
{
arrayClass arr;
std::cout << arr.get(10) << std::endl;
return 0;
}
An alternative, if your arrayClass has a fixed number of elements:
#include <array>
class arrayClass
{
private:
std::array<int, 32> _array;
public:
arrayClass()
: _array{}
{}
size_t size() const {
return _array.size();
}
int get(size_t index){
return _array[index];
}
};
If the extra space a std::vector consumes (usually 4 or 8 bytes) is a real concern, you could make a similar class that only stores the pointer to the allocated memory and the size. It could look like this (but doesn't have the ability to grow/shrink like a vector has):
#include <iostream>
#include <algorithm>
#include <memory>
#include <type_traits>
template<typename T, std::enable_if_t<std::is_default_constructible_v<T>>* = nullptr>
class arrayClass {
public:
using value_type = T;
arrayClass(size_t size = 32) :
_size(size),
_array(std::make_unique<T[]>(_size))
{}
// copy constructor
arrayClass(const arrayClass& rhs) :
_size(rhs._size),
_array(std::make_unique<T[]>(_size))
{
static_assert(std::is_copy_assignable_v<T>, "T must be copy assignable");
std::copy(rhs._array.get(), rhs._array.get() + _size, _array.get());
}
arrayClass(arrayClass&&) = default; // move constructor
// copy assignment operator
arrayClass& operator=(const arrayClass& rhs) {
*this = arrayClass(rhs); // copy construct and move assign
return *this;
}
arrayClass& operator=(arrayClass&&) = default; // move assignment operator
// accessing element at index
T& operator[](size_t index) { return _array[index]; }
const T& operator[](size_t index) const { return _array[index]; }
// bounds checking access to element
T& at(size_t idx) {
if(idx >= _size)
throw std::out_of_range(std::to_string(idx) + ">=" + std::to_string(_size));
return _array[idx];
}
const T& at(size_t idx) const {
if(idx >= _size)
throw std::out_of_range(std::to_string(idx) + ">=" + std::to_string(_size));
return _array[idx];
}
size_t size() const { return _size; }
// support for iterating over the elements in the array
const T* cbegin() const { return _array.get(); }
const T* cend() const { return _array.get() + _size; }
const T* begin() const { return cbegin(); }
const T* end() const { return cend(); }
T* begin() { return _array.get(); }
T* end() { return _array.get() + _size; }
private:
size_t _size;
std::unique_ptr<T[]> _array;
};
using intArray = arrayClass<int>;
int main() {
try {
intArray arr1(10);
// the added iterator support makes range-based for-loops possible:
for(int& v : arr1) {
static int i=0;
v = ++i;
}
intArray arr2;
arr2 = arr1; // copy assign
for(size_t i=0; i < arr2.size(); ++i) {
std::cout << arr2[i] << '\n'; // access using operator[] works too
}
std::cout << arr2.at(10) << '\n'; // throws out_of_range exception
}
catch(const std::out_of_range& ex) {
std::cerr << ex.what() << '\n';
}
}
Constraints: On the target platform I can neither use dynamic memory allocation nor the C++ Standard Library or any other third-party libraries. The language is restricted to C++11 without the usage of compiler specific extensions.
How to handle arrays (or non-owning views) with variable size without dynamic memory allocation and guarantee of valid memory? The size can not be part of the type (as for example with std::array). Arrays can be defined statically. The container type does not need to own the elements but can be initialized with (a pointer to) static memory. The main objective is that the provided data / elements are in valid memory. Secondary would be that the container object provides the number of (valid) elements.
For example the following shall be achieved:
struct S { X x; };
Where S has a fixed size and X is some kind of container pointing to or composed of a number of elements. The number of elements shall not be part of the type X (shall be no class template depending on the size) but is constant for each object and may be a const member.
It must be ensured that the memory pointed to is valid. For example it shall not point to a former automatic variable.
The following things did not work out:
Usage of std::vector (or similar): Would require to implement that container without the use of dynamic memory allocation.
In a similar question it has been suggested to use boost::container::static_vector. The caveat is that the maximum capacity has to be statically served for each object, if I understood it correctly. This is not feasible as it can not be estimated what a suitable limit would be. Also allocating superfluous static memory must be avoided as memory is expensive on the target platform.
std::array can not be used as it contains the size in its type.
Usage of alloca() is out of question.
I did define a non-owning view over a contiguous sequence of objects (resembling template<class T> std::span<T, std::dynamic_extent>) and initialize it with an array with static storage duration. Here is a simplified example:
This is the example type:
template<typename ElementType>
struct Container
{
public:
// type containing size can be used to pass size to `Container()` as template argument
template<ElementType * POINTER, std::size_t N> struct Configuration {};
// constructor accepts pointer to object with static storage duration only
template<ElementType * POINTER, std::size_t N>
Container(const Configuration<POINTER, N>&) : data(POINTER), numberOfElements(N) {}
//! #return number of elements
constexpr std::size_t size() const noexcept { return numberOfElements; }
constexpr ElementType& operator[](const std::size_t index) const noexcept { return data[index]; }
private:
ElementType* const data;
const std::size_t numberOfElements;
};
The restriction to static storage duration of the pointer is achieved by the constructor. This needs an object of Container::Configuration. This in turn requires a pointer as template argument which must have static storage duration (by language definition).
Here is how to use it:
// prints the elements of the container
template<typename T>
void printElements(const Container<T>& container)
{
for(std::size_t i=0; i<container.size(); ++i)
{
std::cout << container[i] << " ";
}
std::cout << std::endl;
}
// array with static storage duration
int globalArray[] = { 1111, 2222, 3333, 4444 };
int main()
{
Container<int>::Configuration<globalArray, sizeof(globalArray)/sizeof(globalArray[0])> arrayConfig;
Container<int> cB(arrayConfig);
printElements(cB);
return 0;
}
It accomplishes that the argument passed to printElements can not point to invalid memory.
This works so far in simple examples. It also contains the size which is useful compared to a plain pointer. And would be even more handy if local static values would have static storage duration in C++11 (they have apparently in C++17).
Before I require this type to be used on a large scale within the target software I wonder if there may be more suitable ways to achieve the desired safety guarantees.
Usage of std::vector (or similar): Would require to implement that container without the use of dynamic memory allocation.
Yes. So you write your own allocator that works on a static pool. Really, implement dynamic allocation, just on a staticcally allocated buffer. I think this is the way you should go.
Theoretically (i've been never able to do it properly) you can estimate the maximum stack usage of your program at compile time in certain environments (no recursive functions, no threads, all libraries are statically linked/bare-metal target, etc...). The rest of the memory can go for a statically allocated buffer for dynamic allocation. But if you can't calculate it, you can estimate some maximum value of free memory you have on your system. Well, if the memory on your platform is precious and you run out of memory, no magic trick is going to help you. Expect re-writting everything to use less memory or moving to a target with more memory.
boost::pool_alloc. And some standard containers like std::vector take an allocator as a template argument. Assuming you have no dynamic allocation, you could link with your own implementation of new() and such functions implementing the standard allocator on your own.
std::array can not be used as it contains the size in its type.
So use std::array and convert it to span.
Or better - write your own std::array that just takes a static buffer with size as the constructor. Or right - it's a span.
And a C++20 span implementation compatible with C++11 is available here. I recommend to use it.
template<typename T>
void printElements(T container)
{
for (auto& i : container)
{
std::cout << container[i] << " ";
}
std::cout << std::endl;
}
// array with static storage duration
int globalArray[] = { 1111, 2222, 3333, 4444 };
#include <tcb/span.hpp>
using tcb::span;
int main()
{
span<int> cB(globalArray,
globalArray + sizeof(globalArray)/sizeof(globalArray[0]));
printElements(cB);
return 0;
}
Also, if you choose to implement your own container, please follow the naming from standard library. Define member types like value_type allocator_type size_type etc. and functions like begin(), push_back() etc. It will be easier for others to use it.
If you need full vector functionality like push_back and pop etc. just implement your own that takes a buffer as a constructor parameter. Like this:
/*
* vector.hpp
*
* Created on: 7 lut 2019
* Author: Kamil Cukrowski
* License: Under GPLv2+ license.
*/
#ifndef STACK_VECTOR_HPP_
#define STACK_VECTOR_HPP_
#include <cstddef>
#include <array>
#include <algorithm>
#include <iterator>
#include <cassert>
namespace stack {
/**
* Vector on statically allocated buffer
*/
template<typename T>
class vector {
public:
using value_type = T;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
using reference = T &;
using const_reference = const T &;
using pointer = T *;
using const_pointer = const T *;
using iterator = T *;
using const_iterator = const T *;
using reverse_iterator = std::reverse_iterator<iterator>;
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
private:
T *_mem{nullptr};
size_type N{0};
size_type _pos{0};
public:
vector(T *_mem, std::size_t count) :
_mem(_mem), N(count) {}
constexpr pointer
data() noexcept
{ return _mem; }
constexpr const_pointer
data() const noexcept
{ return _mem; }
constexpr iterator
begin() noexcept
{ return iterator(data()); }
constexpr const_iterator
begin() const noexcept
{ return const_iterator(data()); }
constexpr iterator
end() noexcept
{ return iterator(data() + _pos); }
constexpr const_iterator
end() const noexcept
{ return const_iterator(data() + _pos); }
constexpr const_iterator
cbegin() const noexcept
{ return const_iterator(data()); }
constexpr const_iterator
cend() const noexcept
{ return const_iterator(end()); }
constexpr const_iterator
max_end() const noexcept
{ return const_iterator(_mem + N); }
constexpr size_type
size() const noexcept
{ return _pos; }
constexpr size_type
capacity() const noexcept
{ return _mem.size(); }
constexpr bool
full() const noexcept
{ return end() == max_end(); }
constexpr bool
empty() const noexcept
{ return begin() == end(); }
constexpr reference
front() noexcept
{ return *begin(); }
constexpr const_reference
front() const noexcept
{ return *begin(); }
constexpr reference
back() noexcept
{ return *(end() - 1); }
constexpr const_reference
back() const noexcept
{ return *(end() - 1); }
constexpr size_type
free() const noexcept
{ return max_end() - end(); }
constexpr const_reference
operator[](size_type idx) const noexcept
{ return _mem[idx]; }
constexpr reference
operator[](size_type idx) noexcept
{ return _mem[idx]; }
constexpr reference
at(size_type idx) {
if (idx > size()) {
throw std::out_of_range("");
}
return this->operator[](idx);
}
constexpr reference
at(size_type idx) const {
if (idx > size()) {
throw std::out_of_range("");
}
return this->operator[](idx);
}
private:
constexpr bool
_is_valid_iterator(const_iterator position) noexcept
{
return begin() <= position && position <= end();
}
constexpr bool
_is_valid_iterator_range(const_iterator first, const_iterator last) noexcept
{
return _is_valid_iterator(first) &&
_is_valid_iterator(last) &&
first < last;
}
public:
constexpr void
clear() noexcept
{ _pos = 0; }
constexpr iterator
insert(iterator position, value_type&& data) noexcept
{
if (!_is_valid_iterator(position)) {
return iterator{};
}
*position = data;
return position;
}
constexpr int
push_back(value_type data) noexcept {
return push_back_n(&data, 1);
}
constexpr int
push_back_n(const_pointer data, std::size_t n) noexcept {
if (free() < n) return -1;
std::copy_n(data, n, end());
_pos += n;
return 0;
}
constexpr int
pop_back() noexcept
{ return empty() ? -1 : ((_pos--), 0); }
constexpr void
resize(size_type count) noexcept
{
if (count <= capacity()) {
_pos = count;
}
}
constexpr void
resize(size_type count, const value_type& value) noexcept {
if (count <= capacity()) {
if (count > size()) {
std::move(end(), end() + count - size(), value);
}
resize(count);
}
}
constexpr void
shift_left(size_type n) noexcept {
std::move(begin() + n, end(), begin());
_pos -= n;
}
constexpr void
shift_left(iterator newbegin) noexcept {
assert(_is_valid_iterator(newbegin));
shift_left(newbegin - begin());
}
constexpr int
shift_right(size_type count, const T& init = T{}) noexcept
{
assert(0);
if (count + size() > capacity()) {
return -1;
}
std::move_backward(begin(), end(), end() + count);
std::fill(begin(), begin() + count, init);
_pos += count;
return 0;
}
constexpr void
input_advance(difference_type n) {
_pos += n;
assert(0 <= _pos && _pos <= capacity());
}
};
}
#endif
It was meant to hold only simple types, so there are no placement new used. For anything more complicated, this is just a start - you would need to call placement_new and proper constructors for element T on ex. push_back and call destructores when doing pop_back() and such operations. And I use noexcept in some places - the code base didn't use any exception, because they usually call dynamic allocation.
Usage example:
#include <iostream>
int main() {
std::array<int, 5> _mem_for_vector;
stack::vector<int> v{_mem_for_vector.data(), 5};
v.push_back(1);
v.push_back(2);
v.push_back(3);
// will print 1 2 3 on separate lines
for (auto& i : v) {
std::cout << i << std::endl;
}
}
I'm developing a software where cache locality is king. It uses about 10 different C arrays in complex data structures that have #define MAX_ARRAY_1_SIZE 1000 like defines. The number of elements in this example 1 array can be then between 0-1000. There are arrays of structs within arrays of structs. The amount of memory used is about 163 megabytes.
However, I noticed C's qsort is slow so I switched to C++ which has much faster std::sort due to inlining the comparator function call. The wonders of template metaprogramming.
Now I'm wondering whether I should use C++'s more advanced features. I tried std::vector in just two of the about 10 arrays, only to find out it kills my cache locality, leading to 15% performance reduction when only two arrays of the about 10 were replaced by std::vector. Yes, I did find out it was due to cache locality: during the critical code path, the array size remains unchanged and it's only populated at program startup time.
Then I tried to see whether there is something that stores the elements in-line, not in a separate dynamically allocated block. I found std::array. However, std::array is a fixed size array and the benefit over C arrays is questionable.
I'm looking for a variable-sized array with maximum size and inline element storage. Is there such a thing in standard C++? Or in Boost?
It doesn't hurt if the variable-sized array actually could have infinite maximum size, so that it stores data in an in-line array if the data fits there, and resorts to using an allocated array if the data doesn't fit in the in-line array.
Now, an experienced C++ programmer could write such a template class in few hours, and I could do the same in few days due to using C++ last time about 10 years ago before I had to obtain the std::sort performance increase now. But I don't want to reinvent the wheel, so I'm looking for existing solutions.
Edit:
To make myself clear, let me provide an example:
struct baz {
int a;
int b;
};
struct bar {
struct baz baz[100];
};
struct foo {
struct bar bar[100];
};
Now struct foo is one contiguous block of memory.
struct baz {
int a;
int b;
};
struct bar {
std::vector<struct baz> baz;
};
struct foo {
std::vector<struct bar> bar;
};
...now it isn't.
There are many small vector classes out there. Boost has some. Try http://www.boost.org/doc/libs/master/doc/html/container/non_standard_containers.html#container.non_standard_containers.small_vector
An easy hand rolled solition with only a bit of overhead would be a gsl span attached to a variant of vector and std array. There is overhead over the most optimal solution of a few bytes. Interact using the span over the unknown container.
I decided to implement my own:
template<class C, class sz_t, sz_t maxsz> class inlinearray {
private:
typedef C value_type;
typedef value_type *pointer;
typedef const value_type *const_pointer;
typedef value_type &reference;
typedef const value_type &const_reference;
typedef value_type *iterator;
typedef const value_type *const_iterator;
typedef sz_t size_type;
typedef std::ptrdiff_t difference_type;
typedef std::reverse_iterator<iterator> reverse_iterator;
typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
sz_t sz;
union {
C realarray[maxsz]; // for correct alignment
char array[maxsz*sizeof(C)];
};
public:
inlinearray()
{
sz = 0;
}
~inlinearray(void)
{
clear();
}
void clear(void)
{
sz_t i;
for (i = 0; i < sz; i++)
{
data()[i].~C();
}
sz = 0;
}
template<class sz2_t, sz2_t maxsz2> inlinearray(inlinearray<C,sz2_t,maxsz2> that)
{
size_t i;
sz = that.sz;
for (i = 0; i < sz; i++)
{
push_back(that[i]);
}
}
template<class sz2_t, sz2_t maxsz2> void operator=(inlinearray<C,sz2_t, maxsz2> val2)
{
swap(val2);
}
void fill(const C& val)
{
std::fill_n(begin(), size(), val);
}
C &operator[](sz_t i) noexcept
{
return data()[i];
}
constexpr const C &operator[](sz_t i) const noexcept
{
return data()[i];
}
C at(sz_t i)
{
if (i >= sz)
{
throw std::out_of_range("inlinerray::at() out of range");
}
return data()[i];
}
constexpr const C at(sz_t i) const
{
if (i >= sz)
{
throw std::out_of_range("inlinerray::at() out of range");
}
return data()[i];
}
void push_back(const C &c)
{
if (sz >= maxsz)
{
abort();
}
new (data()+sz) C(c);
sz++;
}
void pop_back() noexcept
{
data()[sz-1].~C();
sz--;
}
template <class sz2_t, sz2_t maxsz2> void swap(inlinearray<C, sz2_t, maxsz2> &that)
{
if (that.sz > maxsz)
{
abort();
}
if (sz > that.maxsz)
{
abort();
}
std::swap_ranges(begin(), end(), that.begin());
std::swap(sz, that.sz);
}
constexpr sz_t size(void) const noexcept { return sz; }
constexpr sz_t max_size(void) const noexcept { return maxsz; }
constexpr bool empty() const noexcept { return sz == 0; }
C *begin() noexcept { return data(); }
C &front() noexcept { return data()[0]; }
C &back() noexcept { return sz == 0 ? data()[0] : data()[sz - 1]; }
constexpr const C &back() const noexcept { return sz == 0 ? data()[0] : data()[sz - 1]; }
C *end() noexcept { return data() + sz; }
C* data() noexcept { return reinterpret_cast<C*>(array); }
const C* data() const noexcept { return reinterpret_cast<const C*>(array); }
const C *begin() const noexcept { return data(); }
const C *end() const noexcept { return data() + sz; }
const C *cbegin() const noexcept { return data(); }
const C *cend() const noexcept { return data() + sz; }
reverse_iterator rbegin() noexcept { return reverse_iterator(end()); }
reverse_iterator rend() noexcept { return reverse_iterator(begin()); }
const_reverse_iterator rbegin() const noexcept { return const_reverse_iterator(end()); }
const_reverse_iterator rend() const noexcept { return const_reverse_iterator(begin()); }
const_reverse_iterator crbegin() const noexcept { return const_reverse_iterator(end()); }
const_reverse_iterator crend() const noexcept { return const_reverse_iterator(begin()); }
};
Usage of the code should be straightforward.
hello i want to create simple array class to get value and insert value like (array[0] = 4) and this is my program but i have problem to use [] = in same time for insert
template <typename Param>
class arr
{
private:
int Last_point = 0;
Param Data[];
public:
void& operator[]=(int Element_id, Param v)
{
Data[Element_id] = v;
}
Param& operator[] (int Element_id)
{
return Data[Element_id];
}
};
void main()
{
arr <int> Array;
Array[1] = 555;
cout << "Is(" << to_string(Array[1]) << ")" << endl;
system("pause");
}
is there any operator like ([]=)? or for this, i have to use which methods? also i want to get value if just used []
The syntax you are attempting for the operator[] functions is quite wrong. You need something like:
// Version for non-const objects
Param& operator[](std::size_t i)
{
return Data[i];
}
// Version for const objects
Param const& operator[](std::size_t i) const
{
return Data[i];
}
If you want to support arrays whose sizes are known at compile time, you can use:
template <typename Param, std::size_t N>
class arr
{
private:
Param Data[N];
public:
Param& operator[](std::size_t i)
{
return Data[i];
}
Param const& operator[](std::size_t i) const
{
return Data[i];
}
};
If you want to support arrays whose sizes are known at run time, you can use the following. However, you need to be aware of The Rule of Three and make sure to implement the copy constructor and the copy assignment operator properly.
template <typename Param>
class arr
{
private:
Param* Data;
public:
arr(size_t size) : Data(new Param[size]) {}
~arr() { delete [] Data; }
Param& operator[](std::size_t i)
{
return Data[i];
}
Param const& operator[](std::size_t i) const
{
return Data[i];
}
};
Foo& operator[] is sufficient for reading and writing.
Include the size of the array as a template parameter, or replace Param[] with std::vector<Param>.
Use size_t instead of int for array index types.
You don't need to wrap Array[1] in to_string for output.
Don't use void main! The standard says main must be int.
void& is not valid C++ either.
#include <iostream>
using namespace std;
template <typename Param, size_t Size> class arr {
private:
Param Data[Size];
public:
Param &operator[](size_t Element_id) {
return Data[Element_id];
}
};
int main() {
arr<int, 3> Array;
Array[1] = 555;
cout << "Is(" << Array[1] << ")" << endl;
}
However, all that arr does in my snippet is be a less useful std::array!