Lets show it in an example where we have a Data class with primary data, some kind of index that points to the primary data, and we also need to expose a const
version the index.
class Data
{
public:
const std::vector<int>& getPrimaryData() const { return this->primaryData; }
const std::vector<int*>& getIndex() const { return this->index; }
private:
std::vector<int> primaryData;
std::vector<int*> index;
};
This is wrong, as the user can easily modify the data:
const Data& data = something.getData();
const std::vector<int*>& index = data.getIndex();
*index[0] = 5; // oups we are modifying data of const object, this is wrong
The reason of this is, that the proper type the Data::getIndex should be returning is:
const std::vector<const int*>&
But you can guess what happens when you try to write the method that way to "just convert the non-const variant to const variant":
// compiler error, can't convert std::vector<int*> to std::vector<const int*> these are unrelated types.
const std::vector<const int*>& getIndex() const { return this->index; }
As far as I know, C doesn't have any good solution to this problem. Obviously, I could just create new vector, copy the values from the index and return it, but that doesn't make any sense from the performance perspective.
Please, note that this is just simplified example of real problems in bigger programs. int could be a bigger object (Book lets say), and index could be index of books of some sort. And the Data might need to use the index to modify the book, but at the same time, provide the index to read the books in a const way.
CodePudding user response:
In C 20, you can just return a std::span
with elements of type const int*
#include <vector>
#include <span>
class Data
{
public:
std::span<const int* const> getIndex() const { return this->index; }
private:
std::vector<int*> index;
};
int main() {
const Data data;
const auto index = data.getIndex();
*index[0] = 5; // error: assignment of read-only location
}
CodePudding user response:
Each language has its rules and usages... std::vector<T>
and std::vector<const T>
are different types in C , with no possibility to const_cast one into the other, full stop. That does not mean that constness is broken, it just means it is not the way it works.
For the usage part, returning a full container is generally seen as a poor encapsulation practice, because it makes the implementation visible and ties it to the interface. It would be better to have a method taking an index and returning a pointer to const (or a reference to a pointer to const if you need it):
const int* getIndex(int i) const { return this->index[i]; }
This works, because a T*
can be const_casted to a const T *
.
CodePudding user response:
You could return a transforming view to the vector. Example:
auto getIndex() const {
auto to_const = [](int* ptr) -> const int* {
return ptr;
};
return this->index | std::views::transform(to_const);
}
Edit: std::span
is simpler option.
If index
contains pointers to elements of primaryData
, then you could solve the problem by instead storing integers representing the indices of the currently pointed objects. Anyone with access to non-const primaryData
can easily turn those indices to pointers to non-const, others cannot.
primaryData
isn't stable,
If primaryData
isn't stable, and index
contains pointers to primaryData
, then the current design is broken because those pointers would be invalidated. The integer index alternative fixes this as long as the indices remain stable (i.e. you only insert to back). If even the indices aren't stable, then you are using a wrong data structure. Linked list and a vector of iterators to the linked list could work.
CodePudding user response:
You're asking for std::experimental::propagate_const
. But since it is an experimental feature, there is no guarantee that any specific toolchain is shipped with an implementation. You may consider implementing your own. There is an MIT licensed implementation, however. After including the header:
using namespace xpr=std::experimental;
///...
std::vector<xpr::propagate_const<int*>> my_ptr_vec;
Note however that raw pointer is considered evil so you may need to use std::unique_ptr
or std::shared_ptr
. propagate_const
is supposed to accept smart pointers as well as raw pointer types.
CodePudding user response:
When you have the vector<int*>
you have a constant container of pointers to int
s. When you change the *index[0]
, you change the value to which the pointer points, not the value of the pointer as an element of the container (the address it holds).
Thus, the vector itself, as a container of elements with some values, remained unchanged.
To guard against it you'll need to make the pointers in the container const
themselves (note the difference between pointers to const and const pointers)