2

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.

Tohiko
  • 1,860
  • 2
  • 18
  • 26
  • 8
    What about std::vector? – Vlad from Moscow Mar 23 '18 at 13:38
  • 1
    That definitely would be [`std::vector`](http://en.cppreference.com/w/cpp/container/vector). I seems strange that you know about `std::array` but not about the go-to standard container `std::vector`. – Some programmer dude Mar 23 '18 at 13:38
  • I thought `std::vector` would re-allocate the memory on the heap and copy my data to it. – Tohiko Mar 23 '18 at 13:39
  • 3
    And what do you think `SpecialAllocationFunction` would do? If it uses `new[]` then it would *also* allocate on the heap. Perhaps if you try to elaborate on the *actual* problem you have we might be able to help you better. – Some programmer dude Mar 23 '18 at 13:40
  • Ah, so you need a view over your "array"? – juanchopanza Mar 23 '18 at 13:40
  • 1
    If you don't want a copy then `gls::span` should work. – NathanOliver Mar 23 '18 at 13:41
  • When you say "C-style arrays", are you thinking of [VLAs](https://stackoverflow.com/q/1887097/1460794)? – wally Mar 23 '18 at 13:42
  • 1
    Yes, a viewer over my own array. @Some programmer dude , I am trying to abstract my problem. My SpecialAllocationFunction allocates memory in a special memory buffer. – Tohiko Mar 23 '18 at 13:42
  • 3
    std::vector can be used with a special allocator – arynaq Mar 23 '18 at 13:42
  • I appreciate that std::vector can be used with a special allocator. But that's not my use case here. I already have the pointer with an associated size. `gsl::span` seems to be what I need, although not sure if my compiler supports it. I am surprised that the C++ std doesn't have a solution. – Tohiko Mar 23 '18 at 13:46
  • Afaik, there is no such array view container. You could write one yourself, or just use pointers as iterators. – cmaster - reinstate monica Mar 23 '18 at 13:47
  • "I am surprised that the C++ std doesn't have a solution" - and I am still "surprised" that C++ doesn't have true multidimensional VLAs yet as C does since C99... There are some things, the C++ standard committee people don't seem to care about... – cmaster - reinstate monica Mar 23 '18 at 13:49
  • 1
    I was writing an answer and realized everything could be said as a comment: tldr: use `gsl::span` or write a trivial wrapper. – utnapistim Mar 23 '18 at 13:53
  • You don't need one, because standard algorithms (i.e. `std::sort` or `std::find` or whatever) should work fine with pointers. – ivaigult Mar 23 '18 at 14:27

2 Answers2

3

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;
};
Tohiko
  • 1,860
  • 2
  • 18
  • 26
3

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.

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
  • If `arr` had values before creating `vec` (rather than assigning them afterwards), can I access them using `vec`? – Tohiko Mar 23 '18 at 15:37
  • @Tohiko: Not with my original code, because the `construct` method was not overriden. But just look at the edited one – Serge Ballesta Mar 23 '18 at 16:37