How const_iterators are implemented? - c++

#include <iostream>
template <typename T>
struct Node
{
T value;
Node<T>* next;
};
template <typename T>
struct LinkedList
{
// head, tail....
// some implementation...
};
template<
template<typename> typename node,
typename T,
template<typename> typename iterator,
template<typename> typename const_iterator
>
struct SomeComonFunctionsBetweenIterators
{
node<T>* ptr;
// some implementation...
SomeComonFunctionsBetweenIterators(node<T>* ptr) : ptr(ptr) {}
T& operator*() { return ptr->value; }
iterator<T> GetIterator() { return iterator<T>(ptr); }
// doesn't work. want some way like this instead of passing the
// const iterator as a template argument.
//operator const_iterator<T>() { return iterator<const T>(ptr) ; }
operator const_iterator<T>() { return const_iterator<T>(ptr); }
};
template <typename T>
struct LinkedListConstIterator;
template <typename T>
struct LinkedListIterator
: public SomeComonFunctionsBetweenIterators<Node, T, LinkedListIterator, LinkedListConstIterator>
{
LinkedListIterator(Node<T>* ptr)
: SomeComonFunctionsBetweenIterators<Node, T, LinkedListIterator, LinkedListConstIterator>(ptr) {}
// some implementation...
};
template <typename T>
struct LinkedListConstIterator : public LinkedListIterator<T>
{
LinkedListConstIterator(Node<T>* ptr) : LinkedListIterator<T>(ptr) {}
const T& operator*() { return static_cast<const T&>(this->ptr->value); }
};
int main()
{
Node<int> node{ 5, nullptr };
LinkedListIterator<int> it(&node);
std::cout << *it << '\n';
LinkedListConstIterator<int> cit = it;
std::cout << *cit << '\n';
}
In this code I have a linked list and an iterator to it. Also I have a const iterator which inherits from the normal iterator and when dereferenced, returns a const T. The iterator can be for a singly linked list or a doubly linked list and most of the functions in both of them are the same (dereferencing or comparing for example). So I extracted the common functions and put it in a common struct. I want to be able to asign normal iterators to const iterators. So I pass the const iterator to the normal iterator as a template argument and define a conversion operator from the normal iterator to const iterator. This way works but it requires me to always pass the const iterator alongside the normal iterator as a template argument.
Is there any better way to implement const_iterators? instead of passing the const iterator everywhere. How the const_iterators are implemented for example in the STL containers?

How the const_iterators are implemented for example in the STL containers?
You can probably look in your implementation's header files to see how they chose to do it. It will depend on the container type and its internal data structure. But the common pattern I've seen is that there's some private template that can take either non-const T or const T, and that will be used as a base or member of the actual distinct iterator types. One catch is that a specific type from the data structure needs to be used, independently of the type the iterator classes expose. In your example, Node<T> and Node<const T> are not interoperable, so SomeComonFunctionsBetweenIterators could work, but should probably have the node type as a template argument independent of the value type T.
But for an easy way to define iterator types, I always turn to Boost.Iterator's iterator_facade or iterator_adaptor. They take care of a lot of the technical requirements about iterators, letting the author just fill in the behavioral bits. iterator_facade is for implementing something "from scratch", and iterator_adaptor is for the common case of an iterator that contains and wraps another iterator.
The Boost documentation for iterator_facade discusses a way to define related iterator and const_iterator types. (Though their decision to make the iterators interoperable as long as the pointers can convert probably wouldn't be appropriate for iterators from containers.)
Using iterator_facade will also give a bunch of things algorithms may expect of iterators but missing from your code shown, like making std::iterator_traits work, equality and inequality, and other fiddly details.
#include <boost/iterator/iterator_facade.hpp>
#include <type_traits>
template <typename T>
struct Node
{
T value;
Node* next;
};
template <typename NodeType, typename ValueType, typename ContainerType>
class TLinkedListIterator :
public boost::iterator_facade<
TLinkedListIterator<NodeType, ValueType, ContainerType>, // CRTP Derived type
ValueType, // Iterator's value_type
std::forward_iterator_tag // Category
>
{
public:
TLinkedListIterator() : m_node(nullptr) {}
protected:
explicit TLinkedListIterator(NodeType* node) : m_node(node) {}
private:
friend boost::iterator_core_access;
friend ContainerType;
template <typename N, typename V, typename C> friend class TLinkedListIterator;
ValueType& dereference() const { return m_node->value; }
void increment() { m_node = m_node->next; }
// Templating allows comparison between iterator and const_iterator
// in any combination.
template <typename OtherValueType>
bool equal(const TLinkedListIterator<NodeType, OtherValueType, ContainerType>& iter)
{ return m_node == iter.m_node; }
NodeType m_node;
};
template <typename T>
class LinkedList
{
public:
using iterator = TLinkedListIterator<Node<T>, T, LinkedList>;
class const_iterator
: public TLinkedListIterator<Node<T>, const T, LinkedList>
{
private:
using BaseType = TLinkedListIterator<Node<T>, const T, LinkedList>;
public:
const_iterator() = default;
// Converting constructor for implicit conversion from iterator
// to const_iterator:
const_iterator(const iterator& iter)
: BaseType(iter.m_node) {}
protected:
explicit const_iterator(Node<T>* node)
: BaseType(node) {}
friend LinkedList<T>;
};
iterator begin() { return iterator(get_head()); }
iterator end() { return iterator(); }
const_iterator begin() const { return const_iterator(get_head()); }
const_iterator end() const { return const_iterator(); }
const_iterator cbegin() const { return begin(); }
const_iterator cend() const { return end(); }
// ...
};

Related

Custom container list: problem with compiling from iterator to const_iterator

I am trying to recreate some of the C++ containers for a school project and for that I had to also implement iterators. I am currently working on the List container and I am facing a conversion problem.
Here are the parts of the code that are involved:
I have an Elem structure (corresponding to 1 element of a doubly linked list that I use for my List container)
template <class T>
struct Elem
{
Elem *prev;
T data;
Elem *next;
};
a BidirectionalIterator class (used for the list iterators). Here are the constructors:
template <class T>
class BidirectionalIterator
{
public:
typedef BidirectionalIterator iterator;
typedef T value_type;
typedef size_t size_type;
BidirectionalIterator() { _ptr = nullptr; };
BidirectionalIterator(Elem<value_type> *ptr) {
*this->_ptr = ptr;
};
BidirectionalIterator(const iterator &x) {
*this->_ptr = x._ptr;
};
~BidirectionalIterator() {};
iterator &operator=(const iterator &x) {
*this->_ptr = x._ptr;
return (*this);
};
[...]
};
and my list class:
template <class T, class Alloc = std::allocator<T>>
class list
{
public:
typedef T value_type;
typedef BidirectionalIterator<T> iterator;
typedef BidirectionalIterator<const T> const_iterator;
typedef size_t size_type;
/* CONSTRUCTORS */
[...]
list(const list &x) {
_init_list();
assign(x.begin(), x.end());
};
/* ITERATORS */
iterator begin() {
return (iterator(_start));
};
const_iterator begin() const {
return (const_iterator(_start));
};
iterator end() {
return (iterator(_tail));
};
const_iterator end() const {
return (const_iterator(_tail));
};
/* ASSIGN */
void assign(iterator first, iterator last);
void assign(const_iterator first, const_iterator last);
[...]
private:
Elem<value_type> *_head;
Elem<value_type> *_start;
Elem<value_type> *_end;
Elem<value_type> *_tail;
[...]
};
In my main program I' m just calling a function (T being an int) that implicitely calls the copy constructor:
void print_content(ft::list<T> lst);
But when I compile i get this:
./List.hpp:71:12: error: no matching conversion for functional-style cast from 'Elem<ft::list<int, std::allocator<int>
>::value_type> *const' (aka 'Elem<int> *const') to 'ft::list<int, std::allocator<int> >::const_iterator' (aka
'BidirectionalIterator<const int>')
return (const_iterator(_start));
^~~~~~~~~~~~~~~~~~~~~
./List.hpp:53:13: note: in instantiation of member function 'ft::list<int, std::allocator<int> >::begin' requested
here
assign(x.begin(), x.end());
./../Iterator/BidirectionalIterator.hpp:45:3: note: candidate constructor not viable: no known conversion from
'Elem<ft::list<int, std::allocator<int> >::value_type> *const' (aka 'Elem<int> *const') to
'Elem<ft::BidirectionalIterator<const int>::value_type> *' (aka 'Elem<const int> *') for 1st argument
BidirectionalIterator(Elem<value_type> *ptr) {
I don't know how to fix that problem. I already tried to delete the const attribute from my copy constructor and it works, but it needs to be const (for the rest of my project cause I'm implementing the relational operators that call a const list, and also to respect the original container constructor).
Does anyone have an idea?
You try to create an Elem<const int>* from an Elem<int> *const.
I suggest making the iterator's pointer Elem<std::remove_const_t<T>>* (even for a const_iterator) but let dereferencing a const_iterator return a T const& or T const *.
Example:
template <class T>
class BidirectionalIterator {
public:
using value_type = T;
using reference = value_type&;
using pointer = value_type*;
using size_type = std::size_t;
BidirectionalIterator() : _ptr(nullptr) {};
BidirectionalIterator(Elem<std::remove_const_t<value_type>>* ptr) : _ptr(ptr) {};
BidirectionalIterator(const BidirectionalIterator& x) {
_ptr = x._ptr;
};
BidirectionalIterator& operator=(const BidirectionalIterator& x) {
_ptr = x._ptr;
return *this;
};
reference operator*() const { return _ptr->data; }
pointer operator->() const { return &_ptr->data; }
Elem<std::remove_const_t<value_type>>* _ptr;
};
A slightly better version to let you create lists of const Ts and to also let you convert iterators to const_iterators (but not the other way around) to be able to compare iterators could look like this:
#include <memory>
#include <type_traits>
template <class T, class ElemType> // const or non-const T and the type used in Elem
class BidirectionalIterator {
public:
using value_type = T;
using reference = value_type&;
using pointer = value_type*;
using size_type = std::size_t;
BidirectionalIterator() : _ptr(nullptr) {};
BidirectionalIterator(Elem<ElemType>* ptr) : _ptr(ptr) {};
// let a conversion constructor of the const_iterator read _ptr
friend class BidirectionalIterator<const ElemType, ElemType>;
// enable a const_iterator to be created from a non-const iterator via
// a conversion constructor
template<typename U = T, typename V = ElemType,
std::enable_if_t<std::is_const_v<U>&&!std::is_const_v<V>, int> = 0
>
BidirectionalIterator(const BidirectionalIterator<ElemType, ElemType>& x) :
_ptr(x._ptr) {}
// normal copy ctor
BidirectionalIterator(const BidirectionalIterator& x) : _ptr(x._ptr) {}
BidirectionalIterator& operator=(const BidirectionalIterator& x) {
_ptr = x._ptr;
return *this;
};
// the conversion constructor lets you compare a const_iterator and an iterator
bool operator==(const BidirectionalIterator& rhs) const {
return _ptr == rhs._ptr;
}
bool operator!=(const BidirectionalIterator& rhs) const {
return !(_ptr == rhs._ptr);
}
reference operator*() const { return _ptr->data; }
pointer operator->() const { return &_ptr->data; }
private:
Elem<ElemType>* _ptr;
};
// iterator == const_iterator, swap order to use member operator==
template<typename T>
bool operator==(const BidirectionalIterator<T, T>& a,
const BidirectionalIterator<const T, T>& b) {
return b == a;
}
// iterator != const_iterator, swap order to use member operator!=
template<typename T>
bool operator!=(const BidirectionalIterator<T, T>& a,
const BidirectionalIterator<const T, T>& b) {
return b != a;
}
With this iterator definition, you'd need to define your iterator and const_iterator slightly different.
template <class T, class Alloc = std::allocator<T>>
class list {
public:
using value_type = T;
using iterator = BidirectionalIterator<T, T>;
using const_iterator = BidirectionalIterator<const T, T>;
//...

Const methods and custom iterators

I have a class that contains a std::vector, and another that is a custom iterator for that class. It works great for non-const methods, but when I try to create an iterator in a const method, the compiler fails because it can't convert a const_iterator to an iterator. For example, the code below fails, but if the call to ConstFunc is commented out, it compiles and runs fine.
#include <vector>
template <class T>
class FooIter {
public:
typename std::vector<T>::iterator iterator;
FooIter(std::vector<T> &vec) {iterator = vec.begin();}
};
template <class T>
class Foo {
public:
std::vector<T> vec;
Foo() {}
void NonConstFunc() {
FooIter<T> iter(vec);
}
void ConstFunc() const {
FooIter<T> iter(vec);
}
};
int main()
{
Foo<int> foo;
foo.NonConstFunc();
foo.ConstFunc();
return 0;
}
Is there a way to modify FooIter so it can serve as both iterator and const_iterator?
EDIT: A commenter asked "How do you plan to use FooIter<T> in a const method?" Below is one example.
std::vector< std::pair<unsigned, unsigned> > find() const {
std::vector< std::pair<unsigned, unsigned> > list(0);
Matrix2dIterator<T> it = this->begin(true);
Matrix2dIterator<T> itEnd = this->end(true);
for (; it != itEnd; ++it) {
if (*it) {
list.push_back(std::make_pair(it.getRow(), it.getCol()));
}
}
return list;
}
Matrix2d is "Foo", and Matrix2dIterator is "FooIter". find needs to iterate over the std::vector which holds the matrix data, but it is not changing that data.
The reason for this is, that you could modify vec and therefore your class through a std::vector<T>::iterator. So you cannot have them in a const member function. The more technical reason is that in a const member function all members are const and the std::vector has the functions
std::vector<T>::iterator begin();
std::vector<T>::const_iterator begin() const;
std::vector<T>::const_iterator cbegin() const;
so if vec is const begin() will return a const iterator which is also very sensible, since you cannot modify the vector through a const_iterator.
You can adapt your code by providing a ConstFooIter that has a member of type std::vector<T>::const_iterator this would look like the following:
#include <vector>
template <class T>
class ConstFooIter {
public:
typename std::vector<T>::const_iterator iterator;
ConstFooIter(const std::vector<T> &vec) {iterator = vec.cbegin();}
};
Alternatively, you don't template on the value_type of the vector but on the vector itself, this detects const-ness automatically:
#include <vector>
template <class T, class = std::enable_if_t<std::is_same_v<std::remove_cv_t<T>, std::vector<typename T::value_type, typename T::allocator_type>>>>
class FooIter {
public:
decltype(std::declval<T>().begin()) iterator;
FooIter(T &vec) {iterator = vec.begin();}
};
template <class T>
class Foo {
public:
std::vector<T> vec;
Foo() {}
void NonConstFunc() {
FooIter iter(vec);
}
void ConstFunc() const {
FooIter iter(vec);
}
};
int main()
{
Foo<int> foo;
foo.NonConstFunc();
foo.ConstFunc();
return 0;
}
Note that this does not imply that you only have one class left. The iters in your two methods will be of different types (FooIter<std::vector<T>> vs FooIter<const std::vector<T>>). The advantage is that the compiler will do the dirty work of writing the two classes for you and hence you decrease the code duplication since the two classes are nearly the same.
This comes at a disadvantage. It is not that easy to see which variant is used right now and using a const iterator in a non-const context (which you should always do if you do not intend to modify anything) is a bit tricky since FooIter(vec) will always return the non-const variant when vec is not const.
EDIT: Added some template magic to only allow std::vector, i.e. std::set<int> s; FooIter f{s}; won't compile anymore.
You should go for one of n314159's solutions. I'm just showing what the problem is of making your FooIter work for both non-const and const vectors.
You can add an overloaded constructor to FooIter that takes a const reference:
FooIter(const std::vector<T> &vec) {iterator = vec.begin();}
This would be enough, if it were not for the fact that iterator itself is non-const, and so the compiler will rightfully complain about that. You could have two member variables, one non-const and the other const:
typename std::vector<T>::iterator iterator;
typename std::vector<T>::const_iterator const_iterator;
FooIter(std::vector<T> &vec) {iterator = vec.begin();}
FooIter(const std::vector<T> &vec) {const_iterator = vec.begin();}
But then other member functions would need to know which one to use. You could add a flag variable for that, but that would be rather inefficient; it needs more memory (apart from the duplication of the iterator itself), and it needs a check for every member function call. So the best solution is to have two separate iterator classes, one for non-const and the other for const.
maybe..?
#include <vector>
using namespace std;
template <class T>
class FooIter {
public:
const typename vector<T>::iterator itr; // const added
FooIter(vector<T> &vec) : itr{vec.begin()} {}
};
template <class T>
class Foo {
public:
vector<T> vec;
Foo() {}
void NonConstFunc() {
FooIter<T> iter(vec);
}
void ConstFunc() { // const removed, if
FooIter<T> iter(vec); // needed prefix it on each needed line
}
};
int main() {
Foo<int> foo;
foo.NonConstFunc();
foo.ConstFunc();
return 0;
}

Create typedef only when typedef exists in template parameter

I'm trying to create universal container wrapper.
template<typename type>
class ContainerWrapper
{
public:
using allocator_type = typename type::allocator_type;
using size_type = typename type::size_type;
using difference_type = typename type::difference_type;
using pointer = typename type::pointer;
using const_pointer = typename type::const_pointer;
using reference = typename type::reference;
using const_reference = typename type::const_reference;
using iterator = typename type::iterator;
using const_iterator = typename type::const_iterator;
using reverse_iterator = typename type::reverse_iterator;
using const_reverse_iterator = typename type::const_reverse_iterator;
using value_type = typename type::value_type;
iterator begin() noexcept { return container.begin(); }
const_iterator begin() const noexcept { return container.begin(); }
iterator end() noexcept { return container.end(); }
const_iterator end() const noexcept { return container.end(); }
reverse_iterator rbegin() noexcept { return container.rbegin(); }
const_reverse_iterator rbegin() const noexcept { return container.rbegin(); }
reverse_iterator rend() noexcept { return container.rend(); }
const_reverse_iterator rend() const noexcept { return container.rend(); }
const_iterator cbegin() const noexcept { return container.cbegin(); }
const_iterator cend() const noexcept { return container.cend(); }
const_reverse_iterator crbegin() const noexcept { return container.crbegin(); }
const_reverse_iterator crend() const noexcept { return container.crend(); }
protected:
ContainerWrapper() {}
type& getContainer() { return container; }
const type& getContainer() const { return container; }
private:
type container;
};
But not all containers have all kinds of iterators. Is it possible to expose container types only when they exists? Something like
using const_reverse_iterator = typename std::enable_if_t<std::is_class<typename type::const_reverse_iterator>::value, typename type::const_reverse_iterator>::type;
I could use C++11 only (gcc 4.7). Of course I could create different wrappers for different containers, but I prefer to have one universal wrapper.
You could create a base class for each such type, and specialize that base with SFINAE:
template <typename...> struct void_type
{
using type = void;
};
template <typename T, typename = void> struct ContainerWrapper_Base_const_reverse_iterator{};
template <typename T> struct ContainerWrapper_Base_const_reverse_iterator
<T, typename void_type<typename T::const_reverse_iterator>::type>
{
using const_reverse_iterator = typename T::const_reverse_iterator;
};
// ... Similar bases for each conditional alias.
template <typename type> class ContainerWrapper
: public ContainerWrapper_Base_const_reverse_iterator<type>
// ... Inherit from all those bases
{
// ...
};
You could possibly put similar types into a single base, if you're sure that they're always defined together or not defined at all.
Also, you might want to generate those bases with preprocessor, possibly with x-macros.
Also note that the above code could be simplified with C++17 std::void_t or a handwritten template <typename...> using void_t = void;, but neither work properly in GCC 4.7.
Is a raw array container-y enough for you?
Anyway:
Use SFINAE, overload-resolution and inheritance.
namespace detail {
using std::rbegin;
template <class T>
auto reverse_iter_typedefs(long) {
struct {} r;
return r;
}
template <class T, class X = decltype(rbegin(std::declval<T&>()))>
auto reverse_iter_typedefs(int) {
struct {
using reverse_iterator = decltype(rbegin(std::declval<T&>()));
} r;
return r;
}
}
template <class T>
ContainerWrapper : decltype(detail::reverse_iter_typedefs<T>(1)), ...
Is there a reason why your aren't using auto for you functions?
Using HolyBlackCat's code, you should consider grouping types and functions that will clearly (or should) come together, and using auto to avoid ambiguous function calls:
template <typename...> struct void_type { using type = void; };
template<typename type, typename = void>
struct conditional_derive_iterator {
protected:
conditional_derive_iterator(type*const container) {}
};
template<typename type>
struct conditional_derive_iterator
<type, typename void_type<typename type::iterator>::type>
{
// grouping types
using iterator = typename type::iterator;
using const_iterator = typename type::const_iterator;
// this should work with gcc. avoids ambiguity. cleaner code
inline constexpr auto begin() const noexcept { return c->begin(); }
inline constexpr auto end() const noexcept { return c->end(); }
protected:
// this will let you get access to the container
conditional_derive_iterator(type*const container) : c(container) {}
private:
type*const c;
};
template<typename type>
class ContainerWrapper :
public conditional_derive_iterator<type>
{
public:
// for demo purpose. however, make sure to pass &container to your bases
ContainerWrapper(const type& c) :
container(c),
conditional_derive_iterator(&container) {}
// continue code...
};
class UselessType{};
void main()
{
ContainerWrapper<UselessType> a(UselessType{});
ContainerWrapper<std::vector<int>> b({1,2,3});
auto itera = a.begin(); // this will not compile
auto iterb = b.begin(); // this will compile
}
While auto func()... is c++14 and up, I believe that auto func() -> return_type is c++11, so begin/end would look like this:
inline constexpr auto begin() const noexcept -> decltype(iterator{}, const_iterator{}) { return c->begin(); }
Here is an example: https://repl.it/K9g5/0
You should also take a look at this Member Detector: https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Member_Detector
It could come in handy.

Accessing an outer public member from an inner class

I have the following classes and I am trying to overload the operator* from the inner class iterator
#ifndef __LISTMAP_H__
#define __LISTMAP_H__
#include "xless.h"
#include "xpair.h"
template <typename Key, typename Value, class Less=xless<Key>>
class listmap {
public:
using key_type = Key;
using mapped_type = Value;
using value_type = xpair<const key_type, mapped_type>;
private:
Less less;
struct node;
struct link {
node* next{};
node* prev{};
link (node* next, node* prev): next(next), prev(prev){}
};
struct node: link {
value_type value{};
node (node* next, node* prev, const value_type&);
};
node* anchor() { return static_cast<node*> (&anchor_); }
link anchor_ {anchor(), anchor()};
public:
class iterator;
listmap(){};
listmap (const listmap&) = default;
listmap& operator= (const listmap&) = default;
~listmap();
iterator insert (const value_type&);
iterator find (const key_type&);
iterator erase (iterator position);
iterator begin() { return anchor()->next; }
iterator end() { return anchor(); }
bool empty() const { return begin() == end(); }
};
template <typename Key, typename Value, class Less>
class listmap<Key,Value,Less>::iterator {
private:
friend class listmap<Key,Value>;
listmap<Key,Value,Less>::node* where {nullptr};
iterator (node* where): where(where){};
public:
iterator(){}
value_type& operator*();
value_type* operator->();
iterator& operator++(); //++itor
iterator& operator--(); //--itor
void erase();
bool operator== (const iterator&) const;
bool operator!= (const iterator&) const;
};
template <typename Key, typename Value, class Less>
value_type& listmap<Key,Value,Less>::iterator<Key,Value,Less>::operator*()
{
return where->value;
}
#include "listmap.tcc"
#endif
The problem is that value_type is a public member from the class listmap and it's not static, so I don't know how to complete the declaration of operator*(). I wouldn't like to fix the bug by changing the structure of the code. Ex: making
using value_type = xpair<const key_type, mapped_type>;
Global. I am just wondering if there is some other trick I can use to access value_type.
....edit: I have no idea how the inner class recognizes value_type
It's barely the same as for iterator, you just have to add typename keyword
typename listmap<Key,Value,Less>::value_type
staticness doesn't matter for a type.
The alias1 inside iterator
template <typename Key, typename Value, class Less>
class listmap<Key,Value,Less>::iterator {
...
using value_type = typename listmap<Key,Value,Less>::value_type;
};
allows you to write the definition more succinctly using auto suffix type:
template <typename Key, typename Value, class Less>
auto listmap<Key,Value,Less>::iterator::operator*() -> value_type&
{
return where->value;
}
Careful: inner iterator class is not template, only listmap is:
listmap<Key,Value,Less>::iterator<Key,Value,Less>::operator
// ~~~~~~~~~~~~~~~~ remove this
1 Btw don't forget the others.

Iterator equality

I'm creating a container class which implements a double linked list.
template <class T>
class dl_list {
public:
class link {
public:
T* data;
link *prev, *next;
};
class iterator {
link* node;
public:
link* get_node() { return node; }
// ++, --, * operators, etc.
};
// other stuff
};
Pretty neat, I'm having fun with it. But one problem I'm having is when I define my equality operators for the iterator type, I have to do a template specialization.
template <class T>
bool operator==(typename dl_list<T>::iterator& lhv, typename dl_list<T>::iterator rhv) {
return lhv.get_node() == rhv.get_node();
}
will not work, I have to specialize it like so:
bool operator==(typename dl_list<int>::iterator& lhv, typename dl_list<int>::iterator rhv) {
return lhv.get_node() == rhv.get_node();
}
for every type I want to use it for, which is annoying for obvious reasons. How do I get around this?
Make it a member of the iterator class:
bool operator==( const interator& other ) const
{
return node == other.node;
}
You can't. The compiler cannot know that some T is a nested type of some other U. Consider
template<> class dl_list<float> {
public:
typedef dl_list<int>::iterator iterator;
};
You have to take the iterator type directly as the template parameter, or define it as a member of the iterator class, or define the iterator class outside dl_list and simply make a typedef for it inside dl_list.
Easiest cleanest way is to define the operator inside the iterator class:
class iterator
{
public:
...
friend bool operator==(iterator& lhs, iterator& rhs)
{
return lhs.get_node() == rhs.get_node();
}
};
(Bit of a code smell here - I'd have expected get_node() to have a const version, allowing the operator== to accept parameters by const reference...)