Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ Writing const vectors with pointers to non const

I am writing a CustomVector class, internally storing data using a standard vector:

template <class T>
class CustomVector {
  friend class CustomVector_ref<T>;

  public:
  ...

  private:
    std::vector<T> _data;
};

Then, in order to extract subvectors from CustomVector, I use a class storing pointers to each element of data:

template <class T>
class CustomVector_ref {
  public:
    //Returns the value stored in CustomVector 
    //and pointed-to by _data_ref
    T& operator[] (size_t id) { return *_data_ref[id] } 
    const T& operator[] const (size_t id) { return *_data_ref[id] } 
  ...
  private:
    std::vector<T*> _data_ref;
};

Now, to illustrate my problem it is sufficient to consider the simple costructor building a reference to all the elements of CustomVector

template<class T>
CustomVector_ref<T>::CustomVector_ref(CustomVector<T>& cv)
{
  for (T& el : cv._data)
    _data_ref.push_back(&el);
}

That works fine, but if I have a const CustomVector, I need also to define the constructor:

template<class T>
CustomVector_ref<T>::CustomVector_ref(const CustomVector<T>& cv)
{
  for (const T& el : cv._data)
  _data_ref.push_back(const_cast<T*>(&el));
}

That works too, but if the CustomVector_ref object is not declared as const, then with the non-const operator [] it is possible to write data into the const CustomVector object.

const CustomVector<int> cv(...) //CostumVector is somehow constructed, 
                                //that does not matter now

std::cout<<cv[0]<<std::endl;  //output 1 for example

CustomVector_ref<int> cvr(cv)

cvr[0] = 2;

std::cout<<cv[0]<<std::endl;  //now cv[0] stores 2

It is possible to avoid this behavior?

I have noticed that this happens also with standard vectors, for example

const std::vector<int> v(1,1);
std::vector<int*> vp;

vp.push_back(const_cast<int*>(&v[0]));

*vp[0] = 2;

std::cout<<v[0]<<std::endl;  // now v[0] stores 2, not 1

So, since this is standard C++, I do not bother too much to fix my CustomVector, but it would be nice to know if there is a (not too convoluted) solution.

like image 548
Hybridslinky Avatar asked Jul 07 '17 16:07

Hybridslinky


1 Answers

I'm not exactly sure what you are trying to do. Are you trying to prevent modification of the original vector regardless of whether CustomVector_ref was initialized with a const or non-const version of the vector? If so, you can do this by simply changing the return type of operator[], as follows:

template <class T>
class CustomVector_ref {
  public:
    ...
    const T& operator[] (size_t id) { return *_data_ref[id] } 
    const T& operator[] const (size_t id) { return *_data_ref[id] } 
  ...
};

Note that storing pointers into the original vector is dangerous. If the original vector's size changes, all your pointer values may be invalidated.

If you want to change the behavior of CustomVector_ref depending on whether it's constructed with a const or non-const version of the original vector, you'll need to change the signature of the template in order to be able to differentiate between const and non-const versions of the original vector. An example implementation:

#include <iostream>
#include <vector>

template <class T>
class CustomVector_ref {
public:
  CustomVector_ref(T& orig_vector) : m_vector_ref(orig_vector) {}

  auto& operator[] (size_t id) { return m_vector_ref[id]; }
  const typename T::value_type& operator[] (size_t id)  const { return m_vector_ref[id]; }

  private:
  T& m_vector_ref;
};

int main(int argc, char* argv[]) {
  std::vector<int> my_vec({1, 2, 3});
  std::cout << my_vec[0] << std::endl;

  CustomVector_ref<std::vector<int>> cv_ref(my_vec);
  cv_ref[0] = 2; // Assignment is ok; non-const cv_ref initialized with a non-const vector
  std::cout << cv_ref[0] << std::endl;  //now cv[0] stores 2

  CustomVector_ref<const std::vector<int>> cv_cref(my_vec);
  // cv_cref[0] = 2; // Compile error: assignment of read-only location
  const_cast<int&>(cv_cref[0]) = 2; // Explicit override of const
  std::cout << cv_cref[0] << std::endl;

  const std::vector<int> my_const_vec({1, 2, 3});
  // CustomVector_ref<std::vector<int>> cv_cref2(my_const_vec); // Compile error; non-const initialization from const
  CustomVector_ref<const std::vector<int>> cv_cref3(my_const_vec); // Ok, const initialization from const
  // cv_cref3[0] = 2; // Compile error: assignment of read-only location
  const_cast<int&>(cv_cref3[0]) = 2; // Explicit override of const
  std::cout << cv_cref3[0] << std::endl;

  return 0;
}
like image 176
Jay West Avatar answered Oct 26 '22 23:10

Jay West