Background:
I am writing my own vector.h for fun (please don't judge). I am trying to remain const correct so I have implemented both a const .begin() function and non const .begin() functions for my Vector::iterator class. My CMakeLists.txt file is requiring C++23
The Issue:
In my UnitTest I am unfortunately able to declare a const vector then declare a non const iterator and successfully change the value of the const Vector using the iterator. This is obviously not the point of a const Vector.
Please If you have time look through my implementation to see what I am overlooking. Full code found on my github here. Snippets below:
My code:
Test that has unwanted behavior
TEST(testConstBegin) {
const Vector<int> c_myVec = {0,1,2,4};
Vector<int>::iterator it = c_myVec.begin(); // NOTE not const.
// c_myVec[0] = 4; // fails as expected
*it = 4; // should fail but does not. THIS IS WHY I WROTE THIS QUESTION.
CHECK_EQUAL(c_myVec[0], 0); // This line should never run.
}
iterator assigment ctor
template <typename T>
typename Vector<T>::iterator::iterator_reference
Vector<T>::iterator::operator=(
typename Vector<T>::iterator::const_iterator_reference other) {
if (this != &other)
iter_ = other.iter_;
return *this;
}
const .begin()
template <typename T>
typename Vector<T>::const_iterator
Vector<T>::cBegin() const {
return typename Vector<T>::iterator::const_iterator(array_);
}
// issue #001
template <typename T>
typename Vector<T>::const_iterator
Vector<T>::begin() const {
return typename Vector<T>::iterator::const_iterator(array_);
}
.begin()
template <typename T>
typename Vector<T>::iterator
Vector<T>::begin() {
return iterator(array_);
}
const operator*()
// const operator*
template <typename T>
typename Vector<T>::iterator::const_reference
Vector<T>::iterator::operator*() const {
return *iter_;
}
non const operator*()
// operator*
template <typename T>
typename Vector<T>::iterator::reference
Vector<T>::iterator::operator*() {
return *iter_;
}
stl_vector.h implementation
/**
* Returns a read-only (constant) iterator that points to the
* first element in the %vector. Iteration is done in ordinary
* element order.
*/
const_iterator
begin() const _GLIBCXX_NOEXCEPT
{ return const_iterator(this->_M_impl._M_start); }
stl_vector.h behavior I am trying to create
testVectorFillCtor.cpp:32:50: error: conversion from ‘__normal_iterator<const int*,[...]>’ to non-scalar type ‘__normal_iterator<int*,[...]>’ requested
32 | std::vector<int>::iterator it = c_myVec.begin();
| ~~~~~~~~~~~~~^~
Implementing a container is not easy, but if you do, I absolutely respect that your goal is to be const correct. In fact, don't do this kind of thing if you are not going to do it 100% "const-correct".
The original defect I see in your code is this one here,
https://github.com/PIesPnuema/stl_implementation_practice/blob/main/include/vector.h#L38
const_iteratorneeds to be implemented as a different class thaniterator, so it can returnconstreferences when dereferenced.In any case, a
const_iteratorhas nothing to do with the iterator beingconst. Think about it, can aconst_itertorbe mutated (e.g., by increment)? Probably yes, so it is definitely not the same asiterator const. This is the same difference as betweendouble const*anddouble* const. (This is the kind of confusion one doesn't have by using EastConst).In other words,
const_iteratoranditeratorare basically independent types. For the case of a vector, they can be defined simply asdouble const*anddouble*respectively; but that will hide the general complexity of implementing iterators. So, I will assume that we want iterators to be classes.I have implemented vector-like containers; unfortunately, there is no other way.
You can save some code duplication by having a single common
basic_iterator<Ref>base parameterized inT&andT const&, but that is a different story.This is the smallest code I came up with to illustrate this point:
https://godbolt.org/z/vGT8j3WrG
For a complete example, look at my container array library, follow the white rabbit from this line, https://gitlab.com/correaa/boost-multi/-/blob/master/include/multi/array_ref.hpp#L1267
To avoid some code duplication, you can use some template tricks:
https://godbolt.org/z/81GK69G7W
If you want to completely eliminate code duplication and automatically convert from
iteratortocons_iterator, you can do this, although it is unclear if it is a real gain.https://godbolt.org/z/PEqqdTefe
One can eliminate the spurious base class and the strange constructor by using the questionable technique of inheritance-for-extension; one has to be careful later when implementing mutation members on the iterators classes. Note the constness doesn't escape from the design if one is careful in the implementation:
https://godbolt.org/z/Ye1fj5faE