Search code examples
c++immutabilitystdarray

Protect individual values in std::array while allowing complete overwrite


I have an array which is global state. This is running in an embedded/microcontroller environment, not a large application where I might be more concerned about global state.

How can I declare the array such that its members cannot be changed, yet I can still update a copy, and yet still completely overwrite the global array when needed?

I'm interested in using an immutable array for this to be able to quickly check if it has changed when it gets returned from a mutation function. That is, if the memory address has changed, then I know it was copied and updated.

If it has been updated, then I want to overwrite the original global array with the updated version.

However, I want to protect the individual values of the array from being accidentally overwritten in main() or in case the global array is otherwise accessed and directly updated. Maybe I'm paranoid, but for the sake of the question let's say this is a valid concern.

The only updates that should be possible should done via copy / address update.

This version works, but the global array can be accessed and directly updated:

#include <array>
#include <iostream>

std::array<int, 3> arr = {1, 2, 3}; // global array

std::array<int, 3> const * mutate(std::array<int, 3> const * ptr) {
  // (*ptr)[2] = 9; // I like how this is prevented by const
  // return ptr; // uncomment to test no mutation

  // mutation via copy, retain new array on the heap
  std::array<int, 3> * ret = new std::array<int, 3>;
  std::copy((*ptr).begin(), (*ptr).end(), (*ret).begin());
  (*ret)[0] = 9;
  return ret;
}

int main() {
  std::array<int, 3> const * retPtr = mutate(&arr);

  if (retPtr == &arr) {
    std::cout << "pointers are the same\n";
  } else {
    std::cout << "pointers are different\n";
    arr = (*retPtr);
    // do expensive things here with the new state
  }
  delete[] retPtr;

  for (int val : arr) {
    std::cout << val;
    std::cout << "\n";
  }

  return 0;
}

First I tried declaring the array as std::array<int, 3> const. This prevented me from being able to completely overwrite the array.

std::array<int, 3> const arr = {1, 2, 3};

...

arr = (*retPtr); // Compilation error: no viable overloaded '='

Then I tried declaring it as std::array<int const, 3>, but this prevented me from being able to mutate the copy in the mutation function.

std::array<int const, 3> arr = {1, 2, 3};

std::array<int const, 3> * mutate(std::array<int const, 3> * ptr) {
  std::array<int const, 3> * ret = new std::array<int const, 3> {0,0,0};
  std::copy((*ptr).begin(), (*ptr).end(), (*ret).begin());

  // Compilation error: cannot assign to return value because function 'operator[]' 
  // returns a const value
  (*ret)[0] = 9; 

  return ret;
}

Thanks in advance for any help you can offer!


Solution

  • Assuming you are using c++20 or greater, this is now doable. A change in basic life has made possible replacing objects so long as the object replaced can be transparently replaced. This limits you to not changing the length of the array or it's element type as these would change the object type. Note that since std::array can only be initialized by an initializer list, you would have to list each element in such a list so this gets unwieldy for large arrays.

    #include <iostream>
    #include <array>
    #include <memory>
    
    std::array< const int, 3> a{ 1,2,3 };
    void mutate()
    {
        std::destroy_at(&a);  // call destructor. not needed unless array elements have destructors
        std::construct_at(&a, std::array<const int, 3> {2, 3, 4});
    }
    
    int main()
    {
        mutate();
        std::cout << a[0] << '\n';
    }