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.
Related
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.
Research
I found this old answer. I'd like to know if the solution is still effective or if there is a new, more effective way to do it.
Background
Lets suppose I have an iterator like below (specifics don't really matter, only that it is huge):
class inorder_iterator : public std::iterator<std::forward_iterator_tag, token>
{
friend syntax_tree;
node* current_node;
std::stack<node*> prev_nodes;
//std::stack<node*> visited_nodes;
std::map<node*, bool> visited;
public:
inorder_iterator();
inorder_iterator& operator++();
inorder_iterator operator++(int);
token& operator*();
const token& operator*() const;
token* operator->();
const token* operator->() const;
friend bool operator==(const inorder_iterator lhs, const inorder_iterator rhs);
friend bool operator!=(const inorder_iterator lhs, const inorder_iterator rhs);
private:
inorder_iterator(node* current);
node* find_leftmost_node(node* from);
};
Problem
Implementations of the member function declarations are of reasonable size, but I would like to reuse the current iterator to reduce code duplication.
Idea
First idea that came to mind is to templatize on node type, so I could pass const node to make it const iterator, but it just sounds fishy
template <typename Node>
//replace every occurrence of node with Node
// and use decltype(node.tk) instead of token everywhere
Also I'm not sure if this use of const is one of those "const belongs to implementation specifics" case.
A template is probably the only way to avoid code duplication. But I wouldn't leave the type parameter open. I'd simply use a boolean parameter that is fed to a std::conditional in order to determine the type:
template<bool IsConst> class iter_impl {
using value_type = std::conditional_t<IsConst, Node const, Node>;
};
The two iterator types of the container can then be either a couple of aliases, or if you want truly distinct types, a couple of classes that inherit from the template. Like so:
struct const_iterator : iter_impl<true> {};
struct iterator : iter_impl<false> {};
The benefit of using two new classes, is that you can define a converting constructor for const_iterator that allows it to be build from the non const iterator. This is akin to the standard library's behavior.
Also I'm not sure if this use of const is one of those "const belongs to implementation specifics" case.
The fact you use a const Node is indeed an implementation detail. But so long as it gives you the documented type behavior (an iterator to a const member of the container) I wouldn't stress too much over this.
You can safely define class template template< typename value_type > iterator parametrized by value type (const or non-const) with conversion operator to const version (i.e. operator interator< value_type const > () const):
template< typename type >
struct node
: node_base
{
union { type value; };
node() noexcept { ; }
node(node const &) = delete;
node(node &&) = delete;
void operator = (node const &) = delete;
void operator = (node &&) = delete;
~node() { ; }
type * pointer() noexcept { return &value; }
};
template< typename type >
struct tree_iterator
{
using value_type = type;
using reference = type &;
using pointer = type *;
using iterator_category = std::bidirectional_iterator_tag;
using difference_type = std::ptrdiff_t;
using node_type = node< value_type >;
using node_pointer = node_type *;
base_pointer p = nullptr;
pointer operator -> () const noexcept { return node_pointer(p)->pointer(); }
reference operator * () const noexcept { return *operator -> (); }
tree_iterator & operator ++ () noexcept { p = increment(p); return *this; }
tree_iterator operator ++ (int) noexcept { return {std::exchange(p, increment(p))}; }
tree_iterator & operator -- () noexcept { p = decrement(p); return *this; }
tree_iterator operator -- (int) noexcept { return {std::exchange(p, decrement(p))}; }
bool operator == (tree_iterator const & it) const noexcept { return p == it.p; }
bool operator != (tree_iterator const & it) const noexcept { return !operator == (it); }
operator tree_iterator< type const > () const { return {p}; }
};
In using const_iterator = iterator< value_type const >; the latter became no-op and can be called only intentionally (i.e. using syntax it.template operator iterator< value_type const > ()).
The code is given from here - rewrite of libstdc++'s red-black tree.
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.
I'm studying OOP course (C++ is a base language) at university. My task is to implement own linked list template container class. I did it almost completely but faced with problem. It is known that STL provides iterator and const_iterator classes for iteration through list. They have almost the same implementation, the major difference is that iterator's methods return references while const_iterator's methods — constant references. I followed https://stackoverflow.com/a/3582733/2108548 and created separated template class ListIterator. Then I declared with typedef classes Iterator and ConstIterator inside the class List.
I got something like this:
template<typename T>
class ListNode
{
public:
ListNode(T *node_value = nullptr, ListNode *node_prev = nullptr, ListNode *node_next = nullptr):
value(node_value), prev(node_prev), next(node_next) { }
T *value;
ListNode *prev, *next;
};
template<typename T>
class ListIterator
{
typedef ListNode<T> Node;
public:
ListIterator();
ListIterator(Node *node);
ListIterator(ListIterator const &other);
ListIterator &operator++();
// ...
Node *i;
};
template<typename T>
class List: public Container
{
typedef ListIterator<T> Iterator;
typedef ListIterator<T const> ConstIterator;
// ...
Iterator begin() const
{
return Iterator(m_first->next);
}
ConstIterator const_begin() const
{
return ConstIterator(begin());
}
// ...
};
All worked great until I decided to make "copy-constructor" Iterator -> ConstIterator. So I need constructor method that gets ListIterator<T> (where T is the data class name) and creates new object type ListIterator<T const>. But in fact ConstIterator's constructor gets T const as a template parameter, so I need to remove const for constructor's parameter. I found header type_traits which does this. So I wrote "copy-constructor":
typedef typename std::remove_cv::type NoConstT;
ListIterator(ListIterator const &other);
But it doesn't work! I got this error after requesing const_begin():
List<int> list1;
list1 << 1 << 2 << 3;
int i = *list1.const_begin();
error: 'ListIterator<T>::ListIterator(const ListIterator<typename std::remove_cv<_Tp>::type>&) [with T = int; typename std::remove_cv<_Tp>::type = int]' cannot be overloaded with 'ListIterator<T>::ListIterator(const ListIterator<T>&) [with T = int; ListIterator<T> = ListIterator<int>]'
But that's not all. To accomplish my goal have to convert ListNode<T> to ListNode<T const> as well. But I have one more problem there: each list node contains pointers to previous and next nodes and if I try to initialize them in node's constructor I'll get recursion. Of course I can create function that handles converting all ListNode<T> nodes to ListNode<T const> by iteration through them. But I don't like this solution: it has huge overhead!
I asked this question my teacher. He couldn't understand it for several minutes, then when he got it he said: "It's elementary!" — "But I stuck with it for 3-4 hours!" — "If so, throw away const iterators and finalize list container without them. I need time to understand your code" (as you see my code is quite simple in my opinion). As I understood he didn't know answer to this question. But I really want to know how to make it! How can I solve this problem?
Sorry for tons of mistakes — I'm not a native English speaker.
You can indeed use <type_traits>, just not in the way you describe. One approach would be to always declare a constructor from the same type and declare from non-const one conditionally with enable_if only when template argument is indeed not constant. And node should always be non-const, this you can do with remove_const.
#include <type_traits>
template<typename T>
class ListNode
{
//...
};
template<typename T>
class ListIterator
{
typedef ListNode<typename std::remove_const<T>::type> Node;
public:
ListIterator() {}
ListIterator(Node*) {}
ListIterator(ListIterator const&) {}
template <typename U>
ListIterator(ListIterator<U> const&, typename std::enable_if<!std::is_const<U>()>::type* = nullptr) {}
};
template<typename T>
class List
{
public:
typedef ListIterator<T> Iterator;
typedef ListIterator<T const> ConstIterator;
// ...
Iterator begin()
{
return Iterator(/*...*/);
}
ConstIterator const_begin() const
{
return ConstIterator(/*...*/);
}
// ...
};
int main() {
List<int> list;
List<int>::ConstIterator ci = list.const_begin();
List<int>::Iterator i = list.begin();
ci = i; // works fine as expected
i = ci; // fails as expected
i = i; // works fine as expected
ci = ci; // works fine as expected
return 0;
}
I have an STL container whose element type is const std::shared_ptr<MyClass>.
I want to supply two iterator types to the user:
MyContainer::iterator
typedefed as std::vector<const std::shared_ptr<MyClass>>::iterator
(which should be the same type as std::vector<const std::shared_ptr<const MyClass>>::const_iterator
MyContainer::const_iterator
typedefed as std::vector<const std::shared_ptr<const MyClass>>::iterator
(which should be the same type as std::vector<const std::shared_ptr<const MyClass>>::const_iterator
In other words, I want the "const" to refer to the MyClass constness, not shared_ptr constness. The solution I found for getting the second iterator type is getting the first one, which is easy (e.g. using vector::begin), and then converting it to the second type using static_cast (fixme: no need to use const_cast because I'm adding constness, not removing it).
Would that be the common good-design way to achieve that, or there's a better/more common way?
typedefed as std::vector<const std::shared_ptr<MyClass>>::iterator (which should be the same type as std::vector<std::shared_ptr<const MyClass>>::const_iterator
But it probably isn't the same type. Iterators are not just pointers. If the iterator and const_iterator types are defined inside vector then they are completely unrelated types:
template<typename T>
class vector
{
class iterator;
class const_iterator;
// ...
vector<const int> is a different type to vector<int> and so their nested types are also different. As far as the compiler is concerned they are completely unrelated types, i.e. you cannot just move const around to any point in this type and get compatible types:
vector<const shared_ptr<const T>>::iterator
You cannot use const_cast to convert between unrelated types. You can use static_cast to convert a vector<T>::iterator to a vector<T>::const_iterator but it's not really a cast, you're constructing the latter from the former, which is allowed because that conversion is required by the standard.
You can convert a shared_ptr<const T> to a shared_ptr<T> with const_pointer_cast<T> but again only because it's defined to work by the standard, not because the types are inherently compatible and not because it "just works" like plain ol' pointers.
Since vector's iterators don't provide the deep-constness you want, you'll need to write your own, but it's not hard:
class MyClass { };
class MyContainer
{
typedef std::vector<std::shared_ptr<MyClass>> container_type;
container_type m_cont;
public:
typedef container_type::iterator iterator;
class const_iterator
{
typedef container_type::const_iterator internal_iterator;
typedef std::iterator_traits<internal_iterator> internal_traits;
const_iterator(internal_iterator i) : m_internal(i) { }
friend class MyContainer;
public:
const_iterator() { }
const_iterator(iterator i) : m_internal(i) { }
typedef std::shared_ptr<const MyClass> value_type;
typedef const value_type& reference;
typedef const value_type* pointer;
typedef internal_traits::difference_type difference_type;
typedef internal_traits::iterator_category iterator_category;
const_iterator& operator++() { ++m_internal; return *this; }
const_iterator operator++(int) { const_iterator tmp = *this; ++m_internal; return tmp; }
reference operator*() const { m_value = *m_internal; return m_value; }
pointer operator->() const { m_value = *m_internal; return &m_value; }
// ...
private:
internal_iterator m_internal;
mutable value_type m_value;
};
iterator begin() { return m_cont.begin(); }
const_iterator begin() const { return const_iterator(m_cont.begin()); }
// ...
};
That iterator type is mising a few things (operator--, operator+) but they're easy to add, following the same ideas as already shown.
The key point to notice is that in order for const_iterator::operator* to return a reference, there needs to be a shared_ptr<const MyClass> object stored as a member of the iterator. That member acts as a "cache" for the shared_ptr<const MyClass> value, because the underlying container's real elements are a different type, shared_ptr<MyClass>, so you need somewhere to cache the converted value so a reference to it can be returned. N.B. Doing this slightly breaks the iterator requirements, because the following doesn't work as expected:
MyContainer::const_iterator ci = c.begin();
const shared_ptr<const MyClass>& ref = *ci;
const MyClass* ptr = ref.get();
++ci;
(void) *ci;
assert( ptr == ref.get() ); // FAIL!
The reason the assertion fails is that *ci doesn't return a reference to an underlying element of the container, but to a member of the iterator, which gets modified by the following increment and dereference. If this behaviour isn't acceptable you'll need to return a proxy from your iterator instead of caching a value. Or return a shared_ptr<const MyClass> when the const_iterator is dereferenced. (The difficulties of getting this 100% right is one of the reasons STL containers don't try to model deep constness!)
A lot of the effort of defining your own iterator types is done for you by the boost::iterator_adaptor utility, so the example above is only really useful for exposition. With that adaptor you'd only need to do this to get your own custom iterator types with the desired behaviour:
struct iterator
: boost::iterator_adaptor<iterator, container_type::iterator>
{
iterator() { }
iterator(container_type::iterator i) : iterator_adaptor(i) { }
};
struct const_iterator
: boost::iterator_adaptor<const_iterator, container_type::const_iterator, std::shared_ptr<const MyClass>, boost::use_default, std::shared_ptr<const MyClass>>
{
const_iterator() { }
const_iterator(iterator i) : iterator_adaptor(i.base()) { }
const_iterator(container_type::const_iterator i) : iterator_adaptor(i) { }
};
boost::iterator_adaptor makes it pretty easy to define your own iterator types based on another iterator type. So you can set it up so that *iter is a const shared_ptr<MyClass>& or const shared_ptr<const MyClass>& as desired.
Though in the const_iterator case, dereferencing can't return a const shared_ptr<const MyClass>& if what you actually have is shared_ptr<MyClass>. So we'll define const_iterator::reference as just shared_ptr<const MyClass> and return by value.
#include <boost/iterator/iterator_adaptor.hpp>
class MyContainer {
public:
class iterator;
class const_iterator;
class iterator :
public boost::iterator_adaptor<
iterator, // This class, for CRTP
std::vector<const std::shared_ptr<MyClass>>::const_iterator,
// Base type
const std::shared_ptr<MyClass> > // value_type
{
public:
iterator() {}
iterator(const iterator&) = default;
private:
friend class MyContainer; // allow private constructor
friend class boost::iterator_core_access; // allow dereference()
explicit iterator(base_type iter) : iterator_adaptor(iter) {}
const std::shared_ptr<MyClass>& dereference() const
{ return *base_reference(); }
};
class const_iterator :
public boost::iterator_adaptor<
const_iterator, // This class, for CRTP
std::vector<const std::shared_ptr<MyClass>>::const_iterator,
// Base type
const std::shared_ptr<const MyClass>, // value_type
boost::use_default, // difference_type
std::shared_ptr<const MyClass> > // reference_type
{
public:
const_iterator();
const_iterator(const const_iterator&) = default;
// Implicit conversion from iterator to const_iterator:
const_iterator(const iterator& iter) : iterator_adaptor(iter.base()) {}
private:
friend class MyContainer; // allow private constructor
friend class boost::iterator_core_access; // allow dereference()
explicit const_iterator(base_type iter) : iterator_adaptor(iter) {}
std::shared_ptr<const MyClass> dereference() const
{ return *base_reference(); }
};
iterator begin() { return iterator(mVec.begin()); }
iterator end() { return iterator(mVec.end()); }
const_iterator begin() const { return cbegin(); }
const_iterator end() const { return cend(); }
const_iterator cbegin() const { return const_iterator(mVec.begin()); }
const_iterator cend() const { return const_iterator(mVec.end()); }
private:
std::vector<const std::shared_ptr<MyClass>> mVec;
};
shared_ptr and other standard smart pointers are not designed with deep-constness in mind. They are trying to be as close to raw pointer usage as possible, and raw pointer's const-ness does not affect the pointee's const-ness.
Andrei Alexandrescu's Loki::SmartPtr (described in his Modern C++ Design) implements reference counting and deep const-ness as policies, which would give you the effect you're looking for. If you don't mind switching to a non-standard smart pointer in order to get non-standard behavior, that may be one way to go.