1

Today, I'm working on understanding some new-to-me features, particularly std::array and std::vector. Individually, these seem to behave as expected, but I'm very puzzled by the behavior illustrated below:

This version works:

printf("Using pointer:\n");
std::vector<std::array<int, 1>*> vap;
vap.push_back(new std::array<int, 1>());
printf("size of vector is %ld\n", vap.size());

printf("before setting value:\n");
printf("value is %d\n", vap.front()->at(0));
std::array<int, 1>* pvals = vap.front();
pvals->at(0) = -1;
printf("after setting value:\n");
printf("value is %d\n", vap.front()->at(0));

This version doesn't update the array:

printf("Without pointer:\n");
std::vector<std::array<int, 1>> va;
std::array<int, 1> arr = {99};
va.push_back(arr);

Inserting the array like this fails too: va.push_back(std::array<int, 1>());

printf("size of vector is %ld\n", va.size());

printf("before setting value:\n");
printf("value is %d\n", va.front().at(0));
std::array<int, 1> vals = va.front();
vals[0] = -1;
printf("after setting value:\n");
printf("value is %d\n", va.front().at(0));

It's likely obvious what I'm trying to do but, in case it helps, I'll write it in prose:

Loosely, I'm creating a vector of arrays of ints. In the first half of the example, I create a vector of pointers to those arrays, and am able to insert a new array, via a pointer, and then modify the element contained in that array. In the second half, I tried to avoid using pointers. That code seems to insert the array successfully but then does not allow me to alter the element within it.

I'm somewhat surprised that there are zero warnings or errors, either at compile or runtime, and I'm guessing that I'm missing something fundamental. What can I try next?

halfer
  • 19,824
  • 17
  • 99
  • 186
Toby Eggitt
  • 1,806
  • 19
  • 24
  • what doesnt work? This is not very clear – pm100 Apr 28 '22 at 17:12
  • 3
    `std::array vals = va.front();` *copies* the element from the vector. Modifying a copy obviously doesn't affect what is in the vector. Try `std::array& vals = va.front();` instead – UnholySheep Apr 28 '22 at 17:12
  • `vap.push_back(new std::array());` -- There is no need for `new` in your code at all. A `std::vector> vap;` is all you really need, given the code you posted and what you're trying to accomplish. And yes, `front()` returns a reference, but you failed to utilize this fact. – PaulMcKenzie Apr 28 '22 at 17:13
  • 1
    @PaulMcKenzie The question contains both versions, see `va` vs `vap`. The version using a pointer to an array serves as a counter-example to the one that doesn't use a pointer. – François Andrieux Apr 28 '22 at 17:17
  • @UnholySheep, so presumably the creation of a copy is an artifact of the assignment, not of the action of front(), is that correct? – Toby Eggitt Apr 28 '22 at 17:25
  • 3
    @TobyEggitt C++ uses value semantics, for the most part. Every object is its own object. `vals` is its own array, unrelated to whatever was used to initialize it. References are used when you want an alias to an object. Since you want `vals` to *refer to* some existing array then it should be a reference type like `std::array& vals`. – François Andrieux Apr 28 '22 at 17:27
  • 1
    @TobyEggitt Yes. Both vector and array have the same kind of copy semantics like a normal `int` or any other variable. The old c-style arrays are the exception in most cases. – super Apr 28 '22 at 17:28
  • 1
    Side note: 25 years ago C++ was a very different beast. You won't need to start over with introductory material, but I do recommend you get [a good book or two](https://stackoverflow.com/questions/388242/) for reference as you bring yourself back up to speed. – user4581301 Apr 28 '22 at 17:28

2 Answers2

3

You are working on a copy of the array, you need a reference to the array in the vector

int main() {
    vector<array<int, 1>> v;
    array<int, 1> a = { 99 };
    v.push_back(a);
    cout << v[0][0];
    auto& ar = v[0];
    ar[0] = 42;
    cout << v[0][0];
}

gives

 99 42
pm100
  • 48,078
  • 23
  • 82
  • 145
2

Both your va.push_back(arr); and std::array<int, 1> vals = va.front(); statements are making copies of their source operands. Thus, any changes made to those copies won't affect the source arrays.

In the case of the push_back, this is possibly what you want, so that you can (for example) use the same source variable (arr) – after suitable modifications – to push multiple (different) arrays into your vector.

However, in the second case, that copy is almost certainly not what you want. Instead, you should declare a reference variable and assign that to the element of the vector you want to modify.

In the trivial case that your have shown in your code, the usage would be as follows:

//...
std::array<int, 1>& vals = va.front(); // The "&" declares a reference
vals[0] = -1; // This now modifies the (element of the) array referred to.

But note, you can't reassign a reference variable after it has been declared and initialized as referring to a particular source; thus, a subsequent vals = va.at(2); would not do what you might think (it will, in fact, replace the previously referred-to element with a copy of the RHS array). A notable 'exception' to this is when using such a reference variable in a range-based for loop, like this:

for (auto& vals : va) {
   vals[0] = 42; // This will refer to a different "va" element each loop
}

You can, similarly, define such a reference explicitly inside a more traditional for loop (or, in fact, in any loop or block-scope); that will, effectively, create a new reference each time the loop is iterated (actually, this is also what is being done in the above, range-based loop). So, this code will set the first element of each array to the loop's i index:

for (size_t i = 0; i < va.size(); ++i) {
    auto& vals = va.at(i);
    vals[0] = static_cast<int>(i);
}
Adrian Mole
  • 49,934
  • 160
  • 51
  • 83
  • 1
    Yikes, if push_back is copying too, I *definitely* don't want that. In the intro that someone saw fit to edit out, I explained that this is an embedded microcontroller. I can't afford the memory and/or heap fragmentation associated with that much object copying. I shall stick with the pointers version, but at least I understand what's going on now. – Toby Eggitt Apr 28 '22 at 17:58
  • 1
    @TobyEggitt Hmm. I edited my "probably" to "possibly". Not sure why your intro was edited-out but I'm sure it was well-intentioned. – Adrian Mole Apr 28 '22 at 18:03