77

Does someone know the way to define constant-sized vector?

For example, instead of defining

std::vector<int>

it will be

std::vector<10, int>

It should be completely cross-platformed. Maybe an open source class?

CinCout
  • 9,486
  • 12
  • 49
  • 67
Iron-Eagle
  • 1,707
  • 2
  • 17
  • 34

9 Answers9

78

The std::vector can always grow dynamically, but there are two ways you can allocate an initial size:

This allocates initial size and fills the elements with zeroes:

std::vector<int> v(10);
v.size(); //returns 10

This allocates an initial size but does not populate the array with zeroes:

std::vector<int> v;
v.reserve(10);
v.size(); //returns 0
svelten
  • 1,429
  • 13
  • 12
  • if it was a vector of some user defined type that was given a initial size, what would it be initialized to? – simplename Feb 15 '18 at 15:31
  • The answers below that say to use std::array should be accepted – Michael Mar 15 '18 at 19:06
  • 2
    I came here looking for this answer. The question looks a lot like someone looking for an `std::array`, but I for one do not know my size at compile time and do want to just allocate a certain number of elements fixed. – scravy Sep 24 '18 at 14:50
  • 1
    `vector v(10)` --> *allocates initial size and fills the elements with zeroes* i.e. **allocates the space and constructs the object.** while `v.reserve(10)` --> *allocates an initial size but does not populate the array with zeroes.* i.e. **just allocates the space but does not construct the object** -- very important! – Milan Apr 12 '22 at 21:31
74

There is no way to define a constant size vector. If you know the size at compile time, you could use C++11's std::array aggregate.

#include <array>

std::array<int, 10> a;

If you don't have the relevant C++11 support, you could use the TR1 version:

#include <tr1/array>

std::tr1::array<int, 10> a;

or boost::array, as has been suggested in other answers.

juanchopanza
  • 223,364
  • 34
  • 402
  • 480
  • 6
    Although be aware that `array` is different from `vector` in that the data is contained inside the object. For 10 ints you may never notice the difference, but for large arrays you might (for example) notice that they do not have `O(1)` `swap`, whereas large vectors do. – Steve Jessop Jun 21 '12 at 09:24
  • 8
    Also be aware that an array doesn't have a concept of `size` vs `capacity` like vector does. In an array `size` == `max_size`. That is, you can't create an array of size 10 (ie: space for 10 possible elements), but populate only 5 and expect `size` to return 5. – Steve Lorimer Feb 19 '16 at 18:12
  • Also note that std::array does not cast to a generic run time size. If you want to pass different sized arrays to a function, you would have to pass in both the data() and size() – Michael Mar 15 '18 at 22:21
  • @SteveLorimer *"... but for large arrays you might (for example) notice that they do not have O(1) swap, whereas large vectors do."* -- could you please share resources/references that I can look into to understand more about this? Thank you in advance! – Milan Mar 11 '22 at 22:00
  • 1
    @Milan for a vector you swap just the pointer to the underlying array, whereas unless the array is allocated on the heap (thereby allowing you to swap just the pointer to the array) you have to move all the elements. Read more here: https://stackoverflow.com/a/26312602/955273 – Steve Lorimer Mar 12 '22 at 07:00
  • @SteveLorimer Thanks for further clarification. But, I'm still super confused: if I initialize a vector like this: `std::vector vec1 = {1, 2, 3}`, it will/should be allocated on the stack only, right? It will get allocated on the heap, only if I initialize it like this: `std::vector *vec2 = new std::vector({1, 2, 3})`, isn't it? Please correct me if I'm mistaken. Thanks again! – Milan Mar 22 '22 at 13:56
  • 1
    @Milan yes and no - the vector object itself will be stored on the stack, but *internally* it has dynamic storage which is allocated on the heap. The reason for this is that a vector does not have a fixed number of items it can store; you can change the number of elements at run time. As the number of items in the vector increases it may need to allocate a new larger array for its internal storage (dynamically/"on the heap"). So when you `swap` 2 vectors, it's the pointers to these internal arrays which get swapped – Steve Lorimer Mar 22 '22 at 14:30
  • @SteveLorimer Thank you so much for such a quick response and detailed explanation. I appreciate that. Sorry, but the further question (hopefully the last one): If the vector internally has dynamic storage then does it ever/even make any sense to initialize the vector object itself on the heap using `new`? (I think initializing using the smart pointers is a whole different matter here, correct?) – Milan Mar 22 '22 at 15:16
  • 1
    @Milan in general no, I would not see much sense in creating the vector itself on the heap, unless you wanted to differentiate between a "null" vector and an empty vector... but for that you would probably be better served using `std::optional` – Steve Lorimer Mar 22 '22 at 15:23
19

This is an old question but if someone just needs constant-size indexed container with size defined at runtime, I like to use unique_ptr:

// c++14
auto constantContainer = std::make_unique<YourType []> ( size );

// c++11
std::unique_ptr<YourType[]> constantContainer {new YourType[ size ]};


// Access
constantContainer[ i ]
Pari
  • 458
  • 2
  • 6
  • 15
  • Can you add a solution for C++11? Also, will a unique pointer have the same performance as a vector? – thc Apr 24 '19 at 17:43
  • Yes, both have just plain buffer underneath. – Pari May 05 '19 at 20:24
  • Umm, this would be non-copyable,, wouldn't it? – einpoklum Jan 06 '21 at 17:11
  • This is the only real answer to this question, if we don't want to use custom classes (by @ Marcel ) or external libraries ( by @ steve-lorimer ). I am surprised C++ does not have it. – Sohail Si Jul 09 '22 at 18:37
18

If you want a fixed compile-time specified size (ala std::array<T, N>), but you want to be able to populate the vector with varying numbers of elements between 0 and N, then a good option is eastl::fixed_vector.

std::vector:

The size of a std::vector is dynamic - it will allocate required storage dynamically, and you cannot limit the size and enforce an error.

You can however reserve a certain size, and then add elements up that size before it needs to allocate new storage.

vector.size() is initially 0, and increases as you add elementss

std::array:

The size of a std::array is a compile-time constant - it will allocate required storage statically, and you cannot change the size.

array.size() is always the size of the array, and is equal to array.max_size()

eastl::fixed_vector:

The size of an eastl::fixed_vector can be either static or dynamic.

It will allocate a certain number of elements initially, and then if you allow dynamic growth, will allocate dynamically if required.

For the purpose you originally asked for, you can disable growth (via bEnableOverflow in the template instantiation below)

fixed_vector.size() is initially 0, and increases as you add elements.

template<typename T, 
         size_t nodeCount, 
         bool bEnableOverflow = true, 
         typename OverflowAllocator = 
                      typename eastl::type_select<bEnableOverflow,
                                                  EASTLAllocatorType, 
                                                  EASTLDummyAllocatorType>::type>
class fixed_vector;

Simple example:

#include <iostream>
#include <vector>
#include <array>
#include "EASTL/fixed_vector.h"

int main()
{
    std::vector<int> v;
    v.reserve(10);
    std::cout << "size=" << v.size() << " capacity=" << v.capacity() << '\n';

    std::array<int, 10> a;
    std::cout << "size=" << a.size() << " capacity=" << a.max_size() << '\n';

    eastl::fixed_vector<int, 10, false> fv;
    std::cout << "size=" << fv.size() << " capacity=" << fv.capacity() << '\n';

    return 0;
}

Output:

size=0 capacity=10
size=10 capacity=10
size=0 capacity=10

Note that the size of array is 10, whereas vector and fixed_vector are 0

Steve Lorimer
  • 27,059
  • 17
  • 118
  • 213
13

Use std::array

For better readability you can make typedef:

typedef std::array<int, 10> MyIntArray;
Filip Roséen - refp
  • 62,493
  • 20
  • 150
  • 196
Andrew
  • 24,218
  • 13
  • 61
  • 90
11

A std::vector is a dynamic container, there is no mechanism to restrict its growth. To allocate an initial size:

std::vector<int> v(10);

C++11 has a std::array that would be more appropriate:

std::array<int, 10> my_array;

If your compiler does not support C++11 consider using boost::array:

boost::array<int, 10> my_array;
hmjd
  • 120,187
  • 20
  • 207
  • 252
4

This ----> std::vector<10, int> is invalid and causes error. But the new C++ standard has introduced a new class; the std::array. You can declare an array like this:

std::array<int, 5> arr; // declares a new array that holds 5 ints
std::array<int, 5> arr2(arr); // arr2 is equal to arr
std::array<int, 5> arr3 = {1, 2, 3, 4, 5}; // arr3 holds 1, 2, 3, 4, 5

The std::array has constant size and supports iterator/const_iterator/reverse_iterator/const_reverse_iterator. You can find more about this class at http://cplusplus.com/reference/stl/array/.

Rontogiannis Aristofanis
  • 8,883
  • 8
  • 41
  • 58
2

Improving the answer from Pari
I have been using the following class for many years in number crunching applications:

inline void nodeleter(void*) {}

/// Array of T with ownership. Like \see std::unique_ptr<T[]> but with size tracking.
/// @tparam T Element type.
template <typename T>
class unique_array : public std::unique_ptr<T[],void (*)(void*)>
{   size_t Size;
private:
    typedef std::unique_ptr<T[],void (*)(void*)> base;
protected:
    unique_array(T* ptr, size_t size, void (*deleter)(void*)) noexcept : base(ptr, deleter), Size(size) {}
    void reset(T* ptr, size_t size) noexcept { base::reset(ptr); Size = size; }
public:
    constexpr unique_array() noexcept : base(nullptr, operator delete[]), Size(0) {}
    explicit unique_array(size_t size) : base(new T[size], operator delete[]), Size(size) {}
    template <size_t N> unique_array(T(&arr)[N]) : base(arr, &nodeleter), Size(N) {}
    unique_array(unique_array<T>&& r) : base(move(r)), Size(r.Size) { r.Size = 0; }
    void reset(size_t size = 0) { base::reset(size ? new T[size] : nullptr); Size = size; }
    void swap(unique_array<T>&& other) noexcept { base::swap(other); std::swap(Size, other.Size); }
    void assign(const unique_array<T>& r) const { assert(Size == r.Size); std::copy(r.begin(), r.end(), begin()); }
    const unique_array<T>& operator =(const unique_array<T>& r) const { assign(r); return *this; }
    size_t size() const noexcept { return Size; }
    T* begin() const noexcept { return base::get(); }
    T* end() const noexcept { return begin() + Size; }
    T& operator[](size_t i) const { assert(i < Size); return base::operator[](i); }
    unique_array<T> slice(size_t start, size_t count) const noexcept
    {   assert(start + count <= Size); return unique_array<T>(begin() + start, count, &nodeleter); }
};
  • Assignment is possible but this must not change the size (although it would be easy to implement).
  • You may change the size by calling reset, but as the name suggests this discards any data.
  • Move semantics are supported.
  • const instances do not make their content const. Only the storage cannot be changed.
  • It has also limited support for slices not owned by the instance. But they have not lifetime management. You need to care about dangling references yourself.
  • There are some features missing to be fully compatible to the C++ Container requirement, first of all the typedefs. I never needed them so far.
  • It is easy to extend this type for numeric types and add further vector operations. But there is no straight forward automatism since at least std::is_arithmetic<std::complex<double>>::value is false.
Marcel
  • 1,688
  • 1
  • 14
  • 25
0

I found that std::vector actually has max_size() member and there is a customization point in the std library to tune vector behavior accordingly. So I just tuned std::vector to apply a cap on max_size() and narrow size_type. Compiles with c++11 and newer

  • -Werror -Wextra
    • gcc >= 4.9.0
    • clang >= 3.3
  • /W4 /WX /external:anglebrackets /external:W3
    • MSVC >= 19.20

Most of the code is meaningless boilerplate you can easily cut off if you don't need to support cross platform compilation and/or older versions of c++, the only real logic is inside of Allocator members. Use any allocator and vector types that are compatible with the standard and you can have a caped vector. using CapedVector = Caped<uint16_t>::Vector<char>; You can drop Vector and just use template <class T> using CapedVector = std::vector<T, Caped<uint16_t>::Allocator<std::allocator<T>>> if you don't need to narrow size_type

#include <algorithm>
#include <limits>
#include <memory>
#include <vector>
#include <type_traits>

#if __cpp_constexpr >= 201603L
// c++17
#define NODISCARD [[nodiscard]]
#else
#define NODISCARD
#endif

#if __cpp_constexpr > 200704L
// c++14
#define CONSTEXPR constexpr
#else
#define CONSTEXPR
#endif

#ifndef __cpp_concepts
template <class Size, Size maxSize = std::numeric_limits<Size>::max(), class = typename std::enable_if<std::is_unsigned<Size>::value>::type>
#elif defined CONSTEXPR
template <class Size, Size maxSize = std::numeric_limits<Size>::max(), class = std::enable_if_t<std::is_unsigned_v<Size>>>
#else
template <class Size, Size maxSize = std::numeric_limits<Size>::max()> requires(std::is_unsigned_v<Size>)
#endif
struct Caped
{
    using size_type = Size;
    template <class A>
    struct Allocator : A
    {
        using value_type = typename std::allocator_traits<A>::value_type;
        using pointer = typename std::allocator_traits<A>::pointer;
        using const_pointer = typename std::allocator_traits<A>::const_pointer;
        using void_pointer = typename std::allocator_traits<A>::void_pointer;
        using const_void_pointer = typename std::allocator_traits<A>::const_void_pointer;
        using difference_type = typename std::allocator_traits<A>::difference_type;
        using propagate_on_container_copy_assignment = typename std::allocator_traits<A>::propagate_on_container_copy_assignment;
        using propagate_on_container_move_assignment = typename std::allocator_traits<A>::propagate_on_container_move_assignment;
        using propagate_on_container_swap = typename std::allocator_traits<A>::propagate_on_container_swap;
#ifdef __cpp_lib_allocator_traits_is_always_equal
        using is_always_equal = typename std::allocator_traits<A>::is_always_equal;
#endif
        using A::A;

        using size_type = Caped::size_type;

        template <class U>
        struct rebind
        {
            using other = Allocator<typename std::allocator_traits<A>::template rebind_alloc<U>>;
        };
        CONSTEXPR size_type max_size() const noexcept
        {
            using BaseSize = typename std::allocator_traits<A>::size_type;
            static_assert(sizeof(BaseSize) >= sizeof(size_type), "allocator size_type must be bigger than cap type");
            return static_cast<size_type>(std::min<BaseSize>(maxSize, std::allocator_traits<A>::max_size(*this)));
        }
        NODISCARD CONSTEXPR pointer allocate(std::size_t n)
        {
            return n <= max_size() ? std::allocator_traits<A>::allocate(*this, n) : throw std::bad_array_new_length();
        }
#ifdef __cpp_lib_allocate_at_least
        NODISCARD constexpr pointer allocate_at_least(std::size_t n)
        {            
            return n <= max_size() ? std::allocator_traits<A>::allocate_at_least(*this, n) : throw std::bad_array_new_length();
        }
#endif
    };

    template<class T, template <class, class> class V = std::vector, class A = std::allocator<T>>
    struct Vector : V<T, Allocator<A>>
    {
        using Base = V<T, Allocator<A>>;
        using typename Base::value_type;
        using typename Base::allocator_type;
        using typename Base::difference_type;
        using typename Base::reference;
        using typename Base::const_reference;
        using typename Base::pointer;
        using typename Base::const_pointer;
        using typename Base::iterator;
        using typename Base::const_iterator;
        using typename Base::reverse_iterator;
        using typename Base::const_reverse_iterator;
        using Base::Base;
        using Base::assign;
        using Base::insert;

        using size_type = typename allocator_type::size_type;

        CONSTEXPR Vector(size_type count, const T& value, const allocator_type& alloc = allocator_type()) : Base(count, value, alloc){}
#if _MSVC_LANG <= 201402L
        CONSTEXPR explicit Vector(size_type count) : Base(count) {};
        CONSTEXPR explicit Vector(size_type count, const allocator_type& alloc) : Base(count, alloc){}
#else
        CONSTEXPR explicit Vector(size_type count, const allocator_type& alloc = allocator_type()) : V(count, alloc){}
#endif
        CONSTEXPR void assign(size_type count, const T& value) { Base::assign(count, value); }
        CONSTEXPR reference at(size_type pos) { return Base::at(pos); }
        CONSTEXPR const_reference at(size_type pos) const { return Base::at(pos); }
        CONSTEXPR reference operator[](size_type pos) { return Base::operator [](pos); };
        CONSTEXPR const_reference operator[](size_type pos) const { return Base::operator [](pos); };
        CONSTEXPR size_type size() const noexcept { return static_cast<size_type>(Base::size()); }
        CONSTEXPR size_type max_size() const noexcept { return static_cast<size_type>(Base::max_size()); }
        CONSTEXPR size_type capacity() const noexcept { return static_cast<size_type>(Base::capacity()); }
        CONSTEXPR iterator insert(const_iterator pos, size_type count, const T& value) { return Base::insert(pos, count, value); };
        CONSTEXPR void resize(size_type count) { Base::resize(count); };
        CONSTEXPR void resize(size_type count, const value_type& value) { Base::resize(count, value); };
    };
};

godbolt snippet

Anton Dyachenko
  • 169
  • 1
  • 9