1

So, I've been looking at boost::array but it does require default constructor defined. I think the best way of filling this array with data, would be through a push_back(const T&) method. Calling it more times than SIZE (known at compile-time) would result in assert or exception, depending on build configuration. This way it would always contain meaningful data. Does anyone know efficient, portable, reliable implementation of this concept?

andriej
  • 2,118
  • 3
  • 22
  • 28
  • 1
    `push_back` contradicts "fixed size". I think I know what you mean, that it has a fixed upper limit on the size, and that you won't be changing the size once you've populated it. I'm not aware of an implementation of that. – Steve Jessop Oct 06 '10 at 14:41
  • What do you mean by "doesn't require default constructor"? – joshperry Oct 06 '10 at 14:42
  • 3
    `char array[5]` is fixed size, can be templatized, and is stack based. Bonus: it works even in `C`. :D – ereOn Oct 06 '10 at 14:43
  • @Steve: I think he really means he wants a "fixed capacity". – Matthieu M. Oct 06 '10 at 14:43
  • @user467799: guess a `boost::array` doesn't satisfy you ? – Matthieu M. Oct 06 '10 at 14:44
  • @joshperry: standard containers require that the contained type has a no-args constructor. In principle as part of their contract, and in practice if you do anything that actually uses that constructor. – Steve Jessop Oct 06 '10 at 14:44
  • Ah, you don't want it to initialize the contained objects by their default constructor. – joshperry Oct 06 '10 at 14:45
  • @Matthieu Why would you want to use a pointer to walk around default construction? – Šimon Tóth Oct 06 '10 at 15:10
  • this link given by sbi gives a static vector: http://stackoverflow.com/questions/3563591/how-should-a-size-limited-stl-like-container-be-implemented/3564923#3564923. I couldn't used it, but now I can pass it on... – stefaanv Oct 06 '10 at 15:13
  • @Let_Me_Be: because pointers are default constructible (to null) and thus it works well... though unfortunately means allocating on the free store which might not be what the OP likes. – Matthieu M. Oct 06 '10 at 15:14
  • @Matthieu That's what I was asking. Why would you want to add this layer of indirection that is forcing you to use dynamic memory? – Šimon Tóth Oct 06 '10 at 15:15
  • @Let_Me_be: because it removes the `DefaultConstructible` constraint on `T`, which is the heart of the question. Not all objects are `DefaultConstructible`, case in point: those using allocators into `boost::interprocess` memory regions. – Matthieu M. Oct 06 '10 at 15:18
  • @Steve Jessop: Not so. When you create a non-empty container (or resize it) you pass an instance that's copied into those new slots. A default-constructed object is supplied as a default argument, but for a class that doesn't support default construction, you can pass something else. – Jerry Coffin Oct 06 '10 at 15:20
  • @Jerry: sorry, you're right of course. no-args constructor isn't contractually required, I don't know why I imagined it was. – Steve Jessop Oct 06 '10 at 15:26
  • @Matthie But you don't need indirection, so why add it? – Šimon Tóth Oct 06 '10 at 15:26
  • @Mattheiu M. But the questioner asks for a "stack-based" container (in the title and tags but not in the question body). Putting an array of pointers on the stack is "stack-based", but if the objects that the questioner wants to actually be in the container, are not in the container or on the stack at all, then I think you've departed from the spirit of the thing... – Steve Jessop Oct 06 '10 at 15:29
  • @Steve Jessop: I suspect you were thinking it because in a way you were right. You do end up with a 'default object'. The difference is that you get to specify the default for any given operation instead of having to specify a single default for all time as part of the class design. – Jerry Coffin Oct 06 '10 at 15:35

5 Answers5

1

Well, I would have thought that someone would have brought the answer now, however it seems not, so let's go.

What you are wishing for is something I have myself dreamed of: a boost::optional_array<T,N>.

There are two variants:

  • First: similar to boost::array< boost::optional<T>, N >, that is each element may or may not be set.
  • Second: similar to a std::vector<T> (somehow), that is all beginning elements are set and all following ones are not.

Given the previous questions / comments, it seems you would like the second, but it doesn't really matter as both are quite alike.

template <typename T, size_t N>
class stack_vector
{
public:
  bool empty() const { return mSize == 0; }
  size_t size() const { return mSize; }
  size_t capacity() const { return N; }
  size_t max_size() const { return N; }

  T& operator[](size_t i) { return *(this->pfront() + i); }
  /// ...

private:
  T* pfront() const { return reinterpret_cast<T*>(&mStorage); }

  std::aligned_storage< N * sizeof(T), alignof(T) > mStorage;
  size_t mSize; // indicate how many elements are set, from the beginning
};

Let's focus on those very special operations:

template <typename T, size_t N>
void push_back(T const& t)
{
  new (this->pfront() + mSize) T(t); // in place construction
  ++mSize;
}

template <typename T, size_t N>
void clear()
{
  for (size_t i = 0; i != mSize; ++i)
  {
    (this->pfront() + i)->~T();
  }
  mSize = 0;
}

As you can notice, the main difficulty is to remember that:

  • if no element has been built there yet, you need placement new + copy construction instead of assignment.
  • elements that become "obsolete" (ie would be after the last element) should be properly disposed of (ie their destructor be invoked).

There are many operations on traditional STL container that may be tricky to implement. On a vector, element shuffling (due to insert or erase) are perhaps the most stricking examples.

Also note that with C++0x and initializer-lists vector get emplace_back to directly construct an element in place, thus lifting the CopyConstructible requirement, might be a nice boon dependent on your case.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • I'm marking it as answered but I really cannot tell for sure whether there may be any potential problems with this approach (besides the obvious std::aligned_storage which may not be supported on all compilers yet). – andriej Oct 08 '10 at 18:59
0

boost::array<T, 12> ta; is no different from T[12] ta;; if you don't use an initializer list then the elements will be default constructed.

The common workaround would be boost::array<T*, 12> ta; or maybe boost::array<unique_ptr<T>, 12> ta;.

The only way to store by value is to copy, no way around that... This is what initializer lists do:

struct A {
    A(int i):_i(i){ cout << "A(int)" << endl; }
    A(const A& a){ cout << "A(const A&)" << endl; }
    ~A(){ cout << "~A()" << endl; }

    int _i;
};

int main(){
    boost::array<A, 2> ta = {{1, 2}};
}

This outputs:

A(int)
A(const A&)
A(int)
A(const A&)
~A()
~A()
~A()
~A()

http://codepad.org/vJgeQWk5

joshperry
  • 41,167
  • 16
  • 88
  • 103
  • No, that's suboptimal for my needs. I want to store few small objects in small arrays by value, not by address. – andriej Oct 06 '10 at 14:56
  • Using an initializer list might be cumbersome in some scenarios. You can easily change array size by changing compile-time constant in one place but probably you would have to change initializer lists in many other places. – andriej Oct 06 '10 at 15:50
  • I didn't say it wouldn't be cumbersome, I'm just saying it's the only way to do what you want (barring the opaque type hack recommended in another answer). – joshperry Oct 06 '10 at 16:01
0

may be store a boost::variant in your boost::array? make the first parameter an int or something..

i.e.

boost::array<boost::variant<int, foo>, 6> bar;

okay you have to deal with a variant, but it's stack allocated...

Nim
  • 33,299
  • 2
  • 62
  • 101
  • It's a solution but far from being optimal. I guess the boost::optional class is an evidence that what I want is doable - probably the boost::optional implementation can be extended to array. – andriej Oct 06 '10 at 22:52
  • wasn't aware of it till now, just read up on it... neat! thx for the pointer. It's more cleaner than the above... – Nim Oct 06 '10 at 23:07
  • @user467799: Sure, it's doable. But you probably have to write such a class yourself. Watch out for alignment issues. – sellibitze Oct 07 '10 at 03:19
-1

In C++0x you got std::array<type, size> (probably the same as boost::array). You can initialize array data by using fill() or std::fill_n():

std::array<int, 30> array;
array.fill(0);
boost::array<int, 30> barray;
std::fill_n(barray.begin(), 30, 0);

If you want to get it default-initialized at definition you can use copy-ctor:

static std::array<int, 30> const nullarray = {0, 0, 0, ..., 0}; // nullarray.fill(0);
// (...)
std::array<int, 30> array{nullarray};
erjot
  • 1,362
  • 1
  • 9
  • 13
  • If instead of `int` you use a user-defined type then all 30 will be default constructed (when using `fill`), which is what the OP doesn't want. – joshperry Oct 06 '10 at 14:52
  • @joshperry: oh, so that's what `doesn't require default constructor` mean ... ;f So the problem is not with the container - then OP needs to make his default-ctor deleted or private and e.g. use sfinae for default-ctor-check – erjot Oct 06 '10 at 14:54
-1

Why does it have to reside on the stack? Do you have empirical evidence that creating and reserveing a vector is too slow (using a vector seems like the obvious answer)?

Even if it is, you can create a pool of vectors that have space reserved and swap one of the pre-allocated vectors into a local copy. When you're done with the local one, swap it back again (much like the splice trick for lists).

Mark B
  • 95,107
  • 10
  • 109
  • 188