Question

I'd like to simulate a std::vector that has mixed const and non-const elements. More specifically, I want to have functions that operate on a vector and are allowed to see the entire vector but may only write to specific elements. The elements that can and cannot be written will be determined at runtime and may change during runtime.

One solution is to create a container that holds an array of elements and an equal sized array of booleans. All non-const access would be through a function that checks against the boolean array if the write is valid and throws an exception otherwise. This has the downside of adding a conditional to every write.

A second solution might be to have the same container but this time write access is done by passing an array editing function to a member function of the container. The container member function would let the array editing function go at the array and then check that it didn't write to the non-writable elements. This has the downside that the array editing function could be sneaky and pass around non-const pointers to the array elements, let the container function check that all is well, and then write to non-writable elements.

The last issue seems difficult to solve. It seems like offering direct writable access ever means we have to assume direct writable access always.

Are there better solutions?

EDIT: Ben's comment has a good point I should have addressed in the question: why not a vector of const and a vector of non-const?

The issue is that the scenario I have in mind is that we have elements that are conceptually part of one single array. Their placement in that array is meaningful. To use vectors of const and non-const requires mapping the single array that exist in concept to the two vectors that would implement it. Also, if the list of writable elements changes then the elements or pointers in the two vectors would need to be moved about.

Was it helpful?

Solution

I think you can accomplish what you wish with the following class, which is very simplified to illustrate the main concept.

template <typename T>
struct Container
{
   void push_back(bool isconst, T const& item)
   {
      data.push_back(std::make_pair(isconst, item));
   }

   T& at(size_t index)
   {
      // Check whether the object at the index is const.
      if ( data[index].first )
      {
         throw std::runtime_error("Trying to access a const-member");
      }
      return data[index].second;
   }

   T const& at(size_t index) const
   {
      return data[index].second;
   }

   T const& at(size_t index, int dummy) // Without dummy, can't differentiate
                                        // between the two functions.
   {
      return data[index].second;
   }

   T const& at(size_t index, int dummy) const // Without dummy, can't differentiate
                                              // between the two functions.
   {
      return data[index].second;
   }

   std::vector<std::pair<bool, T> > data;
};

Here's a test program and its output.

#include <stdio.h>
#include <iostream>
#include <utility>
#include <stdexcept>
#include <vector>

//--------------------------------
// Put the class definition here.
//--------------------------------

int main()
{
   Container<int> c;
   c.push_back(true, 10);
   c.push_back(false, 20);

   try
   {
      int value = c.at(0); // Show throw exception.
   }
   catch (...)
   {
      std::cout << "Expected to see this.\n";
   }

   int value = c.at(0, 1); // Should work.
   std::cout << "Got c[0]: " << value << "\n";

   value = c.at(1); // Should work.
   std::cout << "Got c[1]: " << value << "\n";

   value = c.at(1, 1); // Should work.
   std::cout << "Got c[1]: " << value << "\n";

   // Accessing the data through a const object.
   // All functions should work since they are returning
   // const&.
   Container<int> const& cref = c;

   value = cref.at(0); // Should work.
   std::cout << "Got c[0]: " << value << "\n";

   value = cref.at(0, 1); // Should work.
   std::cout << "Got c[0]: " << value << "\n";

   value = cref.at(1); // Should work.
   std::cout << "Got c[1]: " << value << "\n";

   value = cref.at(1, 1); // Should work.
   std::cout << "Got c[1]: " << value << "\n";

   // Changing values ... should only work for '1'
   try
   {
      c.at(0) = 100; // Show throw exception.
   }
   catch (...)
   {
      std::cout << "Expected to see this.\n";
   }

   c.at(1) = 200; // Should work.
   std::cout << "Got c[1]: " << c.at(1) << "\n";
}

Output from running the program:

Expected to see this.
Got c[0]: 10
Got c[1]: 20
Got c[1]: 20
Got c[0]: 10
Got c[0]: 10
Got c[1]: 20
Got c[1]: 20
Expected to see this.
Got c[1]: 200
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top