I'm trying to implement a linked list similar to how it might be implemented in the STL. While implementing the iterator, I made some const member functions (so the user can make use of a const iterator) and noticed that I was able to update member variables without getting a compiler error. The code uses templates, but I tested it calling a function that uses begin() and with a const list, so I know that the template functions that modify the member variables were generated by the compiler. Does anyone know why this works? The function in question is the const version of operator++.
Here's a version of my program, with irrelevant details taken out.
template<typename E>
struct Link {
E val {};
Link* next = nullptr;
Link* prev = nullptr;
};
template<typename E>
struct List {
struct Iterator {
Iterator(Link<E>* c) : curr{c} { }
Iterator& operator++();
const Iterator& operator++() const;
const E& operator*() const {return curr->val;}
E& operator*() {return curr->val;}
// ...
private:
Link<E>* curr;
};
// Constructors, etc ...
// Operations ...
E& front() {return head->val;}
const E& front() const {return head->val;}
Iterator begin() {return Iterator{head};}
const Iterator begin() const {return Iterator{head};}
// Other iterator stuff ...
private:
Link<E>* head;
Link<E>* tail;
int sz;
};
/*---------------------------------------------*/
template<typename E>
typename List<E>::Iterator& List<E>::Iterator::operator++() {
curr = curr->next;
return *this;
}
template<typename E>
const typename List<E>::Iterator&
List<E>::Iterator::operator++() const
{
curr = curr->next;
return *this;
}
I think conceptually, it makes sense to make a const version of operator++ even though it modifies member variables. A const iterator actually refers to the contents of the Link pointer being const, which is exactly why it returns a const E& in the dereference operator. Thus, with a const iterator, you can never update the contents of the iterator.
Let me know if there's anything I should include in the code snippet, thank you!
Template functions aren't actually checked for errors until they're instantiated. If you don't call them they just sit there unnoticed, bombs waiting to go off. You'll get a compiler error once you add a call to Iterator::operator++() const.
For example, I added:
int main() {
List<int> list;
const List<int>::Iterator iter = list.begin();
++iter;
}
And now clang complains:
main.cpp:52:10: error: cannot assign to non-static data member within const
member function 'operator++'
curr = curr->next;
~~~~ ^
main.cpp:61:3: note: in instantiation of member function
'List<int>::Iterator::operator++' requested here
++iter;
^
main.cpp:14:25: note: member function 'List<int>::Iterator::operator++' is
declared const here
const Iterator& operator++() const;
^
(Repl)
I think conceptually, it makes sense to make a const version of operator++ even though it modifies member variables. A const iterator actually refers to the contents of the Link pointer being const, which is exactly why it returns a const E& in the dereference operator. Thus, with a const iterator, you can never update the contents of the iterator.
A const iterator should not be mutable, nor have a ++ operator. The STL actually has separate iterator and const_iterator types. A const_iterator embodies the concept you're describing: the iterator is itself mutable, but what it points to is const.
I recommend you follow suit and create a separate ConstIterator class.
Related
I am trying to make my own mini-vector class and I am attempting to replicate some of the functions, but I can not get them to behave the same way when passing calls such as begin() and end() as parameters - the compiler doesn't deduce the right version. Here is an example:
template<typename T>
class Iterator
{
public:
Iterator() {}
};
template<typename T>
class ConstIterator
{
public:
ConstIterator() {}
};
template <typename T>
class MyList {
public:
MyList() {}
Iterator<T> Begin()
{
return Iterator<T>();
}
ConstIterator<T> Begin() const
{
return Iterator<T>();
}
void Insert(ConstIterator<T> it)
{
}
};
int main() {
MyList<int> myList;
myList.Insert(myList.Begin());
}
At myList.Insert(myList.Begin()); it does not try to use the correct version of Begin(), the const one.
From what I can tell in the std::vector implementation, there are two versions of begin() - one returns an iterator and one returns a const_iterator. The only other difference between them is that one is a const method (the one returning a const_iterator).
_NODISCARD _CONSTEXPR20 iterator begin() noexcept {
auto& _My_data = _Mypair._Myval2;
return iterator(_My_data._Myfirst, _STD addressof(_My_data));
}
_NODISCARD _CONSTEXPR20 const_iterator begin() const noexcept {
auto& _My_data = _Mypair._Myval2;
return const_iterator(_My_data._Myfirst, _STD addressof(_My_data));
}
Many methods, like std::vector::insert take a const_iterator parameter:
_CONSTEXPR20 iterator insert(const_iterator _Where, const _Ty& _Val) { // insert _Val at _Where
return emplace(_Where, _Val);
}
_CONSTEXPR20 iterator insert(const_iterator _Where, _Ty&& _Val) { // insert by moving _Val at _Where
return emplace(_Where, _STD move(_Val));
}
However, there is nothing in the insert method that would make the compiler use the const version of begin().
Which means it has to deduce by the return type alone, but as far as I know that's not possible?
How is it achieving it then?
There is no deduction. If myList is not const-qualified, then the non-const version of Begin() is called for myList.Begin(). Otherwise the const version is called. How you use the result of myList.Begin() is not relevant.
The standard library avoids your issue by providing a conversion from the non-const iterator to the const iterator. For example you could give ConstIterator a constructor which accepts a Iterator, which you must have anyway to make the return Iterator<T>(); statement in your const version of Begin() work (assuming that is not a typo).
By some reasons I need to implement a bidirectional iterator, after some time, I got this result (add parameter tells what the side the iterator should move to (to avoid code duplication, when implementing reverse_iterator)):
#include <iterator>
namespace gph {
template <typename T, int add> class BidirectionalIterator;
template<typename T, int add>
void swap(BidirectionalIterator<T, add>& it1, BidirectionalIterator<T, add>& it2) {
it1.swap(it2);
}
template<typename T, int add>
class BidirectionalIterator {
private:
T *currentPosition, *begin, *end;
public:
using difference_type = std::ptrdiff_t;
using value_type = T;
using pointer = T*;
using reference = T&;
using iterator_category = std::bidirectional_iterator_tag;
inline BidirectionalIterator(T* currentPosition, T* begin, T* end):currentPosition(currentPosition), begin(begin), end(end) {}
//copy constructor
inline BidirectionalIterator(const BidirectionalIterator& iterator)
:BidirectionalIterator(iterator.currentPosition, iterator.begin, iterator.end) {}
//move constructor
inline BidirectionalIterator(BidirectionalIterator&& iterator) noexcept
:BidirectionalIterator(iterator.currentPosition, iterator.begin, iterator.end) {}
//copy and move assignment statement
inline BidirectionalIterator& operator=(BidirectionalIterator iterator) {
gph::swap(*this, iterator);
}
inline void swap(BidirectionalIterator& iterator) {
std::swap(currentPosition, iterator.currentPosition);
std::swap(begin, iterator.begin);
std::swap(end, iterator.end);
}
inline reference operator*() const {
return *currentPosition; //dangerous if the iterator is in not-dereferenceable state
}
inline BidirectionalIterator& operator++() {
if (currentPosition != end) currentPosition += add;
return *this;
}
inline bool operator==(const BidirectionalIterator& iterator) const {
return currentPosition == iterator.currentPosition;
}
inline bool operator!=(const BidirectionalIterator& iterator) const {
return !(*this == iterator);
}
inline BidirectionalIterator operator++(int) {
BidirectionalIterator past = *this;
++*this;
return past;
}
inline BidirectionalIterator& operator--() {
if (currentPosition != begin) currentPosition -= add;
return *this;
}
inline BidirectionalIterator operator--(int) {
BidirectionalIterator past = *this;
--*this;
return past;
}
};
}
I've tried to satisfy MoveAssignable, MoveConstructible, CopyAssignable, CopyConstructible, Swappable, EqualityComparable, LegacyIterator, LegacyInputIterator, LegacyForwardIterator, LegacyBidirectionalIterator named requirements.
Some of their requirements are expressed in operators overloading, but some ones from them I do not know how to implement (perhaps, they are automatically implemented by other ones?), for instance: i->m or *i++ (from here). First question: how should I implement them?
Second question: is my iterator implementation good? What are its drawbacks, where did I make mistakes?
P.S. The questions are on edge of unconstructiveness, but I really need help with it. Sorry for my english.
I find it hard to find a definitive answer to this, so just some thoughts, which may be uncomplete and are open to discussion.
i->m can be implemented by inline pointer operator->() { return this->currentPosition; }
*i++ should already be covered by your implementation
I don't see any reason to swap all the pointers in operator=. For three reasons:
You are swapping the values with a local variable
The move constructor doesn't swap any values (would be inconsistent behaviour between BidirectionalIterator newIt=oldIt; and BidirectionalIterator newIt(oldIt);, but it actually isn't because of the previous point)
Those pointers are not unique resources, so there is no problem in copying them and sharing them between multiple instances
operator= is missing a return.
You have using difference_type = std::ptrdiff_t; but don't implement operator- which would return difference_type, why not implement it?
Reverse iterators can be implemented easier by std::reverse_iterator which will just wrap your iterator and invert ++ and -- and so on.
You probably want to find an easy way to implement a const version of the iterator (a version that always returns a const T& or const T*). I saw three versions of this:
duplicating all the code
using const cast
using an additional template parameter bool TIsConst and using pointer = std::conditional_t<TIsConst, const T*, T*>;
using a templated iterator with parameter const T on the other hand might appear easy, but fails to satisfy a requirement, see this question
This relates to my previous question here.
Here's a summary of that thread: I am trying to implement a doubly-linked-list class called my_list in C++, including iterators and I want automatic implicit conversion of iterator to const_iterator.
My original thought was to have something like
template<class T>
class my_list_iterator<T>
{
node<T> pos_;
/* code */
operator my_list_iterator<const T>(){return my_list_iterator<const T>(pos_);}
};
and then inside my_list define iterator as my_list_iterator<T> and const_iterator as my_list_iterator<const T>.
However, as pointed out by Miled Budneck in the previous thread, this won't work because a pointer to node<T> cannot convert to a pointer to node<const T>. He suggested I re-implement the const iterator as a pointer to const node instead.
While that works, by looking around I also found one other possible way to define the iterator class:
template<class T, class Pointer, class Reference>
class my_list_iterator {
node<T>* pos_;
/* code */
operator my_list_iterator<T,const Pointer,const Reference>() {return my_list_iterator<T,const Pointer,const Reference>(pos_);}
};
Then I can define my_list<T>::iterator as my_list_iterator<T,T*,T&> and my_list<T>::const_iterator as my_list_iterator<T,const T*,const T&>. I thought the last line would take care of the implicit conversion issue, but it seems to not be used at all.
For reference, here is the complete code:
template<class T> class node {
node(const T& t = T()):data(t),next(0),prev(0) {}
T data;
node* next;
node* prev;
friend class my_list<T>;
template<class U,class Pointer,class Reference> friend class my_list_iterator;
};
template<class T,class Pointer,class Reference> class my_list_iterator {
public:
// increment and decrement operators
my_list_iterator operator++();
my_list_iterator operator++(int);
my_list_iterator operator--();
my_list_iterator operator--(int);
// bool comparison iterators
bool operator==(const my_list_iterator& other) const {return pos_==other.pos_;}
bool operator!=(const my_list_iterator& other) const {return pos_!=other.pos_;}
// member access
Reference operator*() const {return pos_->data;}
Pointer operator->() const {return &(pos_->data);}
// conversion to constant
operator my_list_iterator<T,const Pointer,const Reference>() {return pos_;}
private:
node<T>* pos_;
explicit my_list_iterator(node<T>* p=0):pos_(p) {}
friend class my_list<T>;
};
and the error message in the end
note: no known conversion for argument 1 from ‘my_list<int>::iterator’ {aka ‘my_list_iterator<int, int*, int&>’} to ‘const my_list_iterator<int, const int*, const int&>&’
So why does this code not work? Is there any small modification I can make to make it work, or is this design impossible to implement?
Given Pointer = T*, const Pointer is T* const, not const T*. Type substitution is not like macro expansion. const Pointer adds a top-level const to the type denoted by Pointer. Similarly, const Reference is T& const (which is automatically adjusted to T& because top-level cv-qualifiers on references are ignored) instead of const T&. (See What is the difference between const int*, const int * const, and int const *? for the difference between T* const and const T*.)
You need to use something like const std::remove_pointer<Pointer>* for this to work.
I am writing a linked list implementation, but I've gotten stuck on how to implement a const iterator accessed through cbegin() alongside the normal iterator. Here's my attempt at a solution:
#include <iostream>
using namespace std;
template <typename T>
class ListNode {
public:
ListNode<T>* next_;
ListNode<T>* prev_;
T data_;
ListNode():
next_(nullptr),
prev_(nullptr)
{}
};
template <typename T>
class ListIterator
{
typedef ListNode<T> node;
typedef ListNode<T>* pointer;
pointer p_;
public:
ListIterator(pointer p) : p_(p) {}
T& operator*() { return p_->data_; }
};
template<typename T>
class List
{
public:
typedef ListNode<T> node;
typedef ListNode<T>* pointer;
typedef ListIterator<T> iterator;
typedef ListIterator<const T> constIterator;
List() :
head_(nullptr),
tail_(nullptr),
size_(0)
{}
void pushBack(pointer p) {
p->next_ = nullptr;
p->prev_ = nullptr;
if (size_ == 0) {
head_ = p;
} else {
tail_->next_ = p;
p->prev_ = tail_;
}
tail_ = p;
++size_;
}
iterator begin() { return head_; }
iterator end() { return nullptr; }
constIterator cbegin() { return head_; }
constIterator cend() { return nullptr; }
private:
pointer head_;
pointer tail_;
unsigned int size_;
};
class Dog {
public:
int age;
};
int main() {
auto list = List<Dog>();
auto dogNode = ListNode<Dog>();
list.pushBack(&dogNode);
auto b = list.cbegin();
}
When I try to run this, I get the error
error: no viable conversion from returned value of type 'List<Dog>::pointer' (aka 'ListNode<Dog> *') to function return type
'List<Dog>::constIterator' (aka 'ListIterator<const Dog>')
This error makes sense to me, but I can't figure out a good workaround that doesn't involve writing a separate ConstListIterator class (this works, but it feels wrong to copy-pasta all the operator overloading code from ListIterator--most of it omitted from this sample).
In this question, the author uses the approach of parametrizing ListIterator with TNode, which in this context would be ListNode<Dog> rather than Dog. The problem with this approach is that then the operator* can't return an actual Dog, because the ListIterator class doesn't know about the typename Dog. So that solution just hardcodes in the type of data_, so you lose all the benefits of the metaprogramming.
Is there a trick to make this work nicely, or should I just have a separate ConstListIterator class?
Thank you!
Having the node type be a template argument for the iterator is reasonable and allows
using constIterator=ListIterator<const ListNode<T>>;
which conveniently precludes structural modifications as well as content modifications. It allows cbegin to work as written (implicitly adding const to head_’s type). It also allows T to be const itself (which works for a linked list: it never needs to relocate anything).
To define operator*, you can just use auto:
auto& operator*() const {return p_->data_;}
The constness of *p_ carries over to data_ and thence to the return type.
Before C++14
In C++11, you can equip ListNode with
using value_type=T;
and use type traits like is_const and conditional to derive const T from const ListNode<T>.
In C++03, you can directly write a SFINAE-based trait for the purpose.
The reason for your error is that you are trying to return head, which is of type ListNode<T>* from a function with the return type constIterator AKA ListIterator<const T>.
To answer your question, the only difference between an iterator and a const_iterator is that a const_iterator returns a const reference to the value_type of the container instead of just a reference. Therefore, passing const T as the template argument should result in the desired functionality. Since operator* returns T&, if T is instead const T, then it returns const T&, which is correct. It seems that the issue you were having was because you were returning the wrong types from your iterator functions, as noted above.
Your iterator functions should instead look like this:
iterator begin() { return iterator{head_}; }
iterator end() { return iterator{nullptr}; }
constIterator cbegin() { return constIterator{head_}; }
constIterator cend() { return constIterator{nullptr}; }
This returns iterator and constIterator objects that are constructed using the pointers head_ and nullptr, which is what you want.
The template parameter you used for the iterators, the value_type of the list, is correct.
This is a code a got for now but it doesnt compile due to an incomplete variable type container::iterator error. The class and the iterator are still pretty simple since I am trying to figure out how to organise my code.
template <typename T> class container {
using size_type = std::size_t;
using pointer = T*;
using const_pointer = const T*;
public:
class iterator;
container (size_type n=0): data {n>0 ? new T[n]:nullptr}, size {n>0 ? n:0}{};
container (iterator begin, iterator end){};
~container (){ delete[] data; };
private:
pointer data;
size_type size;
};
template <typename T> class container<T>::iterator {
public:
iterator (pointer p): current {p} {};
iterator& operator++ (){ current++; return *this; };
iterator& operator-- (){ current--; return *this; };
reference operator* (){ return *current; };
bool operator== (const iterator& b) const { return current == b.current; };
bool operator!= (const iterator& b) const { return current != b.current; };
private:
pointer current;
};
I would like to keep the iterator definition out of the container class if possible. Thanks for any replies.
container (iterator begin, iterator end){};
That uses iterator but at that point in the code there's no definition of iterator, only a declaration. You'll need to define the structure of your iterator before you can even declare this constructor.
You could alter this constructor to take references. Then you can declare it within your class but then define it below the definition for iterator. IMO this isn't the best way to go, but it's possible.
I would like to keep the iterator definition out of the container class if possible.
It isn't, because the arguments for container's member functions cannot be properly analysed without a definition for their type.
Move it inline.