I'm currently implementing a forward list (Singly linked list).
I noticed that std::forward_list::emplace_after is O(1), so the complexity is constant: How is this possible?
I'm asking because:
You can have the position of the new element to be in the middle,
As far as I know, the only way to find the position where a new element has to be added is to traverse the list with a loop.
Am I missing something?
This is how I currently implement the function.
constexpr void emplace_after(iterator position, Args...args) { // Must be O(1)
size_type index_position = std::distance(begin(), position);
Node* temp = m_head;
for (size_type index{ 0 }; index < index_position; ++index) {
temp = temp->next;
}
Node* next_temp = temp->next;
Node* current_node = new Node(std::forward<Args>(args)...);
temp->next = current_node;
current_node->next = next_temp;
temp = nullptr;
next_temp = nullptr;
m_size += 1;
}
And this is my current forward iterator implementation:
template<typename T>
struct forward_iterator {
Node* m_iterator;
using value_type = T;
using reference = value_type&;
using pointer = value_type*;
using iterator_category = std::forward_iterator_tag;
using difference_type = std::ptrdiff_t;
constexpr forward_iterator(Node* forw_iter = nullptr) : m_iterator{ forw_iter } {}
constexpr Node* getNodeAddress() const noexcept {
return m_iterator;
}
constexpr Node* getNodeNextAddress() const noexcept {
return m_iterator->next;
}
constexpr reference operator*() const noexcept {
return m_iterator->data;
}
constexpr pointer operator->() const noexcept {
return m_iterator;
}
constexpr forward_iterator& operator++() noexcept {
m_iterator = m_iterator->next;
return *this;
}
constexpr forward_iterator operator++(int) noexcept {
forward_iterator tmp(*this);
this = (this)->next;
return tmp;
}
constexpr friend bool operator== (const forward_iterator& first, const forward_iterator& second) noexcept {
return (first.m_iterator == second.m_iterator);
}
constexpr friend bool operator!=(const forward_iterator& first, const forward_iterator& second) noexcept {
return !(first.m_iterator == second.m_iterator);
}
};
You seem to have a misunderstanding about how iterators are supposed to work in singly linked lists. They are not just an identifier you can (and have to) search for, they should point to an actual node in one way or another.
Illustration:
Iterator ------------------+
|
V
[Head] -> Node -> Node -> Node -> Node -> Node -> nullptr
This means your class iterator should contain a Node * (Edit: As it already does)
Then your emplace_back can look like this:
constexpr void emplace_after(iterator position, Args... args)
{
Node* temp = position.getNodeAddress(); // retrieve the node position is referring
Node* next_temp = temp->next;
Node* current_node = new Node(std::forward<Args>(args)...);
temp->next = current_node;
current_node->next = next_temp;
temp = nullptr;
next_temp = nullptr;
m_size += 1;
}
The argument of std::forward_list<T,Allocator>::emplace_after is an iterator, which means you need to find the position before emplacing. Therefore, it just need constant time.
Iterators let you go to the node they refer to in O(1) time.
If your iterator does not, fix it.
Related
template <typename T>
class LinkedList {
struct node;
class Iterator;
public:
LinkedList() {}
LinkedList(std::initializer_list<T> init_list) {
this->operator=(init_list);
}
template <typename InputIterator>
LinkedList(InputIterator first, InputIterator last) {
for (; first != last; ++first)
this->push_back(*first);
}
LinkedList(const LinkedList& another) {
this->operator=(another);
}
~LinkedList() {
while (this->head) {
node* old_head = this->head;
this->head = old_head->next;
delete old_head;
}
}
Iterator begin() {
return Iterator(this->head);
}
Iterator end() {
return Iterator(this->tail->next);
}
I tried to add an empty node at the tail->next, however I can't get the result that I want. And without the empty node I just get a segmentation fault when I run the code.
class Iterator {
friend class LinkedList;
public:
using iterator_category = std::bidirectional_iterator_tag;
using value_type = T;
using difference_type = int;
using pointer = T*;
using reference = T&;
Iterator(node* ptr) : ptr(ptr) {}
Iterator(const Iterator& other) {
this->operator=(other);
}
Iterator& operator=(const Iterator& that) {
this->ptr = that.ptr;
return *this;
}
Iterator& operator++() {
this->ptr = ptr->next;
return *this;
}
Iterator operator++(int) {
Iterator tmp(*this);
this->operator++();
return tmp;
}
Iterator& operator--() {
this->ptr = ptr->prev;
return *this;
}
Iterator operator--(int) {
Iterator tmp(*this);
this->operator--();
return tmp;
}
bool operator!=(Iterator that) const { return !(this->operator==(that)); }
bool operator==(Iterator that) const { return this->ptr == that.ptr; }
T& operator*() const { return ptr->data; }
Iterator* operator->() { return this; }
private:
node* ptr = nullptr;
};
Here is the main function I used to test and when I print the stl_list end() it prints the size of the list. I'm confused what should be returned for this function. I thought it was supposed to be an empty nullptr that points to the location after the tail.
int main() {
std::list<int> stl_list{1, 2, 3, 4, 5};
cs19::LinkedList<int> our_list{1, 2, 3, 4, 5};
std::cout << *stl_list.begin() << '\n';
std::cout << *our_list.begin() << '\n';
std::cout << *our_list.end() << '\n';
std::cout << *stl_list.end() << '\n';
}
Confused on how to implement the end function for doubly linked list
You could create a special node type that is one past the end node which only has a prev pointer.
template <typename T>
class LinkedList {
struct empty_node { // used for end iterator
empty_node* prev = nullptr;
};
struct node : empty_node {
empty_node* next;
T data;
};
Your LinkedList would then have an instance of empty_node whos only purpose is to let prev point back to the last real node. You'd then instantiate the end() iterator with a pointer to this empty_node.
You'd then use empty_node* everywhere until stepping the iterator forward or dereferencing an iterator.
Example with comments to explain:
template <typename T>
class LinkedList {
// node definitions as above
public:
class Iterator {
public:
using iterator_category = std::bidirectional_iterator_tag;
using value_type = T;
using difference_type = int;
using pointer = T*;
using reference = T&;
Iterator(empty_node* ptr) : ptr(ptr) {}
Iterator(const Iterator& other) : ptr(other.ptr) {}
Iterator& operator=(const Iterator& that) {
ptr = that.ptr;
return *this;
}
Iterator& operator++() {
// If the user steps forward, the iterator can't be at the end
// or the program will have undefined behavior as per the usual
// contract, so a cast is fine:
ptr = static_cast<node*>(ptr)->next;
return *this;
}
Iterator operator++(int) {
Iterator tmp(*this);
++*this;
return tmp;
}
Iterator& operator--() {
ptr = ptr->prev;
return *this;
}
Iterator operator--(int) {
Iterator tmp(*this);
--*this;
return tmp;
}
bool operator==(Iterator that) const { return this->ptr == that.ptr; }
bool operator!=(Iterator that) const {
return !(*this == that);
}
// Dereferencing is not allowed if the iterator is at the end so
// cast is fine:
reference operator*() const { return static_cast<node*>(ptr)->data; }
pointer operator->() { return &static_cast<node*>(ptr)->data; }
private:
empty_node* ptr = nullptr;
};
LinkedList() = default;
template <typename InputIterator>
LinkedList(InputIterator first, InputIterator last) {
for (; first != last; ++first) this->push_back(*first);
}
// Delegate to ctor taking iterators:
LinkedList(std::initializer_list<T> init_list)
: LinkedList(init_list.begin(), init_list.end()) {}
// Copy ctor - delegate to ctor taking iterators too:
LinkedList(const LinkedList& another)
: LinkedList(another.begin(), another.end()) {}
~LinkedList() {
// As long as it's not pointing at end, cast is fine:
for(empty_node* next; head != &beyond_end; head = next) {
next = static_cast<node*>(head)->next;
delete static_cast<node*>(head);
}
}
void push_back(const T& value) {
// Create a new node where `prev` points at the current last real node
// and `next` points at our empty end node:
node* nn = new node{{beyond_end.prev}, &beyond_end, value};
// link it:
if (head != &beyond_end) { // not the first node added
// the previous node must be a real node, so cast is fine:
static_cast<node*>(beyond_end.prev)->next = nn;
} else { // the first node added
head = nn;
}
beyond_end.prev = nn; // link beyond_end to the last real node
}
Iterator begin() { return Iterator(head); }
Iterator end() { return Iterator(&beyond_end); } // use `beyond_end` for end()
private:
empty_node* head = &beyond_end; // start pointing at the empty node
empty_node beyond_end; // note, not a pointer
};
So, instead of a node* tail; you'll have an instance of an empty_node in your LinkedList. It will have the same size as a node* so it doesn't waste space.
Demo
You could also store both the head and tail pointer in an empty_node to remove all casts except when dereferencing/deleteing.
template <typename T>
class LinkedList {
struct empty_node { // used for end iterator
empty_node* prev = nullptr;
empty_node* next = nullptr;
};
struct node : empty_node {
T data;
}
It requires minor changes to the example:
Demo
Consider a standard implementation of class Link of LinkedList in c++.
I want to know if its a good idea to overload the operator ++ in this class (I noticed that I repeat the line link = link->next; a lot when dealing with linked lists, so I thought it would be easier if I overload this operator). Here's my implementation:
#ifndef LINK_H
#define LINK_H
#include <iostream>
#include "typeinfo.h"
#include "LinkedList.h"
template <class T> class LinkedList; //|Forward declaration of the generic LinkedList.
template <class T>
class Link
{
public:
//|-------------------- Constructors --------------------
Link(T data): m_data(data), next(NULL){}
//|-------------------- Methods --------------------
T getData(){
return m_data;
}
T& getNext(){
return next;
}
void setNext(Link* newLink){
next = newLink;
}
void setData(T data){
m_data = data;
}
//|-------------------- Operator overload --------------------
bool operator==(Link& other){
if(this->m_data == other.m_data)
return true;
return false;
}
void operator++(){ //Is this okay?
this = this->next;
}
//|-------------------- Friend functions --------------------
friend std::ostream& operator<<(std::ostream& out,const Link<T>& link){
out<<link.m_data;
return out;
}
//|-------------------- Destructor --------------------
virtual ~Link(){}
protected:
public:
//|-------------------- Private fields --------------------
T m_data;
Link<T>* next;
friend class LinkedList<T>;
};
#endif // LINK_H
I guess the way that I tried to do it is not good (it does work as I expected). I tried to use this because I want it to work on pointer that is pointing to a certain link.
So, is it a good idea? if it is, what is the right way to implement it?
Thanks.
Maybe you should refactor your design and the code.
The link, or better said the Node, is normally implemented as an own class. An this class is embedded in the LinkedList class. And that Node class should be completely encapsulated and not shown to the outside world.
The user of the class will just deal with the data value. All the pointer stuff should be hidden.
And to be able to iterate over your class, which is similar to a std::forward_list, you can add ultra simple iterator functionality. Most functions can be implemented using a one liner.
I will show you below an ultra simple implementation example. This may be extended easily according to your needs.
From that you may take over some ideas to improve your design.
With the added iterator functionality, you may use range based for loops and std:: algorithms and all the like.
Please have a look and ask questions, if there should be something unclear.
#include <iostream>
#include <iterator>
#include <initializer_list>
#include <algorithm>
// Very simple implementation of a forward list
template <class T>
class SinglyLinkedList {
// The node
struct Node {
T data{}; // Data. Would normally be a templated argument
Node* next{}; // And the pointer to the next node
Node(const T& i, Node* n = nullptr) : data(i), next(n) {}; // Simple constructor to set a value and next pointer
};
Node* head{}; // This is the start of the list
// It would be advisable to have a tail pointer. We use the more inefficient approach here
Node* getLast() const { Node* n{ head }; while (n and n->next) n = n->next; return n; }
public:
// Constructor / Destructor --------------------------------------------------------------------------------------------------------
~SinglyLinkedList() { clear(); }
// Default constuctor
SinglyLinkedList() {} // Default
// From an initialization list
SinglyLinkedList(const std::initializer_list<T>& il) { clear(); for (const T& i : il) push_back(i); } // From initializer list
// Copy constructor
SinglyLinkedList(const SinglyLinkedList& other) { clear(); for (const T &i : other) push_back(i); }
// Move constructor. Will steal the elements from the other
SinglyLinkedList(SinglyLinkedList&& other) noexcept { head = other.head; other.head = nullptr; }
// Assignment operator
SinglyLinkedList& operator = (const SinglyLinkedList& other) { clear(); for (const T &i : other) push_back(i); }
// Move assignment operator
SinglyLinkedList& operator = (SinglyLinkedList&& other) { head = other.head; other.head = nullptr; }
// Housekeeping --------------------------------------------------------------------------------------------------------------
void clear() { Node* tmp{ head }; while (tmp) { Node* toDelete{ tmp }; tmp = tmp->next; delete toDelete; } head = nullptr; }
int empty() const { return head == nullptr; }
int size() const { int k{}; Node* n{ head }; while (n) { ++k; n = n->next; } return k; }
// Modify content --------------------------------------------------------------------------------------------------------------
void push_front(const T& i) { Node* n = new Node(i); n->next = head; head = n; };
void push_back(const T& i) { Node* n = new Node(i); Node* l = getLast(); if (l) l->next = n; else head = n; }
void pop_front() { if (head) { Node* tmp = head->next; delete head; head = tmp; } }
void pop_back() { // This is a little bit more difficult in a singly linked list
if (head) {
Node* n{ head }, * previous{};
while (n and n->next) {
previous = n;
n = n->next;
}
delete n;
if (previous)
previous->next = nullptr;
else
head->next = nullptr;
}
}
// Access elements --------------------------------------------------------------------------------
T front() const { return head ? head->data : 0; };
T back() const { Node* n = getLast(); return n ? n->data : 0; }
// Add iterator properties to class ---------------------------------------------------------------
struct iterator { // Local class for iterator
Node* iter{}; // Iterator is basically a pointer to the node
// Define alias names necessary for the iterator functionality
using iterator_category = std::forward_iterator_tag;
using difference_type = std::ptrdiff_t;
using value_type = T;
using pointer = T*;
using reference = T&;
// Constructor
iterator() {}
iterator(Node* n) : iter(n) {}
// Dereferencing
reference operator *() const { return iter->data; }
pointer operator ->() const { return &iter->data; }
// Aithmetic operations
iterator& operator ++() { if (iter) iter = iter->next; return *this; }
iterator operator ++(int) { iterator temp{ *this }; ++* this; return temp; }
iterator operator +(const difference_type& n) const { iterator temp{ *this }; difference_type k{ n }; while (k--)++temp; return temp; }
iterator& operator +=(const difference_type& n) { difference_type k{ n }; while (k--)++* this; return *this; };
// Comparison
bool operator != (const iterator& other) const { return iter != other.iter; }
bool operator == (const iterator& other) const { return iter == other.iter; }
bool operator < (const iterator& other) const { return iter < other.iter; }
bool operator > (const iterator& other) const { return iter > other.iter; }
bool operator <= (const iterator& other) const { return iter <= other.iter; }
bool operator >= (const iterator& other) const { return iter >= other.iter; }
// Difference. Also complicated, because no random access
difference_type operator-(const iterator& other) const {
difference_type result{};
Node* n{ iter };
while (n and n != other.iter) {
++result;
n = n->next;
}
return result;
}
};
// Begin and end function to initialize an iterator
iterator begin() const { return iterator(head); }
iterator end() const { return iterator(); }
// Functions typcical for forward lists ----------------------------------------------------------------------
// Easy, becuase we can operate form the current iterator and do not need the "previous" element
iterator insertAfter(iterator& pos, const T& i) {
iterator result{};
if (pos.iter and pos.iter->next) {
Node* n = new Node(i, pos.iter->next);
pos.iter->next = n;
result = n;
}
return result;
}
iterator eraseAfter(iterator& pos) {
iterator result{};
if (pos.iter and pos.iter->next) {
Node* tmp = pos.iter->next->next;
delete pos.iter->next;
pos.iter->next = tmp;
result = pos.iter->next;
}
return result;
}
};
// Test/Driver Code
int main() {
// Example for initilizer list
SinglyLinkedList<int> sllbase{ 5,6,7,8,9,10,11,12,13,14,15 };
// Show move constructor
SinglyLinkedList<int> sll(std::move(sllbase));
// Add some values in the front
sll.push_front(4);
sll.push_front(3);
sll.push_front(2);
sll.push_front(1);
// Delete 1st element (Number 1)
sll.pop_front();
// Delete last element
sll.pop_back();
// Use a std::algorithm on our custom linked list. Works because we have an interator
SinglyLinkedList<int>::iterator iter = std::find(sll.begin(), sll.end(), 8);
// Now add an element after 8
iter = sll.insertAfter(iter, 88);
// End delete the 9
iter = sll.eraseAfter(iter);
// Use range based for loop. Works because, we have iterators
for (int i : sll)
std::cout << i << ' ';
}
I'm stuck on this because my program is built for the user of iterators but in this project it also asks us to return the first element on the list not using iterators. I'm a beginner level programmer so my initial thought was returning the head but obviously it can't be that easy because of all the scoping. I get this error:
invalid initialization of reference of type 'int&' from expression of type 'List::Node'
in function Object &raw_front().
Code is below:
#ifndef LIST_H
#define LIST_H
#include <algorithm>
using namespace std;
template<typename Object>
class List {
private:
// The basic doubly linked list node.
// Nested inside of List, can be public
// because the Node is itself private
struct Node {
Object data;
Node *prev;
Node *next;
Node(const Object &d = Object{}, Node *p = nullptr, Node *n = nullptr)
: data{d}, prev{p}, next{n} {}
Node(Object &&d, Node *p = nullptr, Node *n = nullptr)
: data{std::move(d)}, prev{p}, next{n} {}
};
public:
class const_iterator {
public:
// Public constructor for const_iterator.
const_iterator() : current{nullptr} {}
// Return the object stored at the current position.
// For const_iterator, this is an accessor with a
// const reference return type.
const Object &operator*() const { return retrieve(); }
const_iterator &operator++() {
current = current->next;
return *this;
}
const_iterator operator++(int) {
const_iterator old = *this;
++(*this);
return old;
}
const_iterator &operator--() {
current = current->prev;
return *this;
}
const_iterator operator--(int) {
const_iterator old = *this;
--(*this);
return old;
}
bool operator==(const const_iterator &rhs) const { return current == rhs.current; }
bool operator!=(const const_iterator &rhs) const { return !(*this == rhs); }
protected:
Node *current;
// Protected helper in const_iterator that returns the object
// stored at the current position. Can be called by all
// three versions of operator* without any type conversions.
Object &retrieve() const { return current->data; }
// Protected constructor for const_iterator.
// Expects a pointer that represents the current position.
const_iterator(Node *p) : current{p} {}
friend class List<Object>;
};
class iterator : public const_iterator {
public:
// Public constructor for iterator.
// Calls the base-class constructor.
// Must be provided because the private constructor
// is written; otherwise zero-parameter constructor
// would be disabled.
iterator() {}
Object &operator*() { return const_iterator::retrieve(); }
// Return the object stored at the current position.
// For iterator, there is an accessor with a
// const reference return type and a mutator with
// a reference return type. The accessor is shown first.
const Object &operator*() const { return const_iterator::operator*(); }
iterator &operator++() {
this->current = this->current->next;
return *this;
}
iterator operator++(int) {
iterator old = *this;
++(*this);
return old;
}
iterator &operator--() {
this->current = this->current->prev;
return *this;
}
iterator operator--(int) {
iterator old = *this;
--(*this);
return old;
}
protected:
// Protected constructor for iterator.
// Expects the current position.
iterator(Node *p) : const_iterator{p} {}
friend class List<Object>;
};
public:
List() { init(); }
~List() {
// Place your code here.
clear( );
delete head;
delete tail;
}
List(const List &rhs) {
init();
for (auto &x : rhs)
push_back(x);
}
List &operator=(const List &rhs) {
List copy = rhs;
std::swap(*this, copy);
return *this;
}
List(List &&rhs) : theSize{rhs.theSize}, head{rhs.head}, tail{rhs.tail} {
rhs.theSize = 0;
rhs.head = nullptr;
rhs.tail = nullptr;
}
List &operator=(List &&rhs) {
std::swap(theSize, rhs.theSize);
std::swap(head, rhs.head);
std::swap(tail, rhs.tail);
return *this;
}
// Return iterator representing beginning of list.
// Mutator version is first, then accessor version.
iterator begin() { return iterator(head->next); }
const_iterator begin() const { return const_iterator(head->next); }
// Return iterator representing endmarker of list.
// Mutator version is first, then accessor version.
iterator end() { return iterator(tail); }
const_iterator end() const { return const_iterator(tail); }
// Return number of elements currently in the list.
int size() const { return theSize; }
// Return true if the list is empty, false otherwise.
bool empty() const { return size() == 0; }
void clear() {
while (!empty())
pop_front();
}
// front, back, push_front, push_back, pop_front, and pop_back
// are the basic double-ended queue operations.
Object &front() { return *begin(); }
Object &raw_front() {
// Return the element at the front of the list *without* using the iterator classes
// (You may assume the list is not empty)
// (Yes, this is bad code. I just needed something that will allow the stub to compile.)
return head ;
}
const Object &front() const { return *begin(); }
Object &back() { return *--end(); }
Object &raw_back() {
// Return the element at the end of the list *without* using the iterator classes
// (You may assume the list is not empty)
// Place your code here.
// (Yes, this is bad code. I just needed something that will allow the stub to compile.)
return tail;
}
const Object &back() const { return *--end(); }
void push_front(const Object &x) { insert(begin(), x); }
void push_back(const Object &x) { insert(end(), x); }
void raw_push_front(const Object &x) {
// insert x at the head of the list *without* using the iterator classes
// Place your code here.
Node *p = new Node(x, nullptr, head);
if(tail == nullptr)
tail = p;
head = p;
++theSize;
}
void raw_push_back(const Object &x) {
// insert x at the tail of the list *without* using the iterator classes
// Place your code here.
Node *p = new Node(x, tail, nullptr);
if(head == nullptr)
head = p;
tail = p;
++theSize;
}
void raw_insert_in_order(const Object &x) {
// insert x into the sorted list *without* using the iterator classes.
// You may assume the list is either empty or correctly sorted.
// Place your code here.
}
void push_front(Object &&x) { insert(begin(), std::move(x)); }
void push_back(Object &&x) { insert(end(), std::move(x)); }
void pop_front() { erase(begin()); }
void pop_back() { erase(--end()); }
// Insert x before itr.
iterator insert(iterator itr, const Object &x) {
Node *p = itr.current;
++theSize;
return iterator(p->prev = p->prev->next = new Node{x, p->prev, p});
}
// Insert x before itr.
iterator insert(iterator itr, Object &&x) {
Node *p = itr.current;
++theSize;
return iterator(p->prev = p->prev->next = new Node{std::move(x), p->prev, p});
}
// Erase item at itr.
iterator erase(iterator itr) {
Node *p = itr.current;
iterator retVal(p->next);
p->prev->next = p->next;
p->next->prev = p->prev;
delete p;
--theSize;
return retVal;
}
iterator erase(iterator from, iterator to) {
for (iterator itr = from; itr != to;)
itr = erase(itr);
return to;
}
void splice(iterator position, List<Object> &lst ) {
// Removes all the items from lst, add them prior to position in *this.
// You may assume lst and *this are different lists.
// **Your routine must run in constant time.**
Node *p = position.current;
theSize += lst.size();
p->prev->next = lst.head->next;
lst.head->next->prev = p->prev;
lst.tail->prev->next = p;
p->prev = lst.tail->prev;
lst.init();
}
private:
int theSize;
Node *head;
Node *tail;
void init() {
theSize = 0;
head = new Node;
tail = new Node;
head->next = tail;
tail->prev = head;
}
};
#endif
I am trying to create linked list class and define the iterators, I have all of them except the last one. I dont get how to fix, i get this error when i compile my code:
no match for ‘operator!=’ in ‘recList.SortedList::begin with T = Record != recList.SortedList::end with T = Record’
a1q1main.cpp:114:37: note: candidates are:
sortedlist.h:112:11: note: SortedList::iterator SortedList::iterator::operator!=(bool) [with T = Record, SortedList::iterator = SortedList::iterator]
sortedlist.h:112:11: note: no known conversion for argument 1 from ‘SortedList::iterator’ to ‘bool’
It keeps on displaying this error no matter what i do
I have declared operator == and everything is fine but the != complains
Here is the code :
class SortedList {
struct Node {
T data_;
Node* next_;
Node* prev_;
Node(const T& data = T{}, Node* next = nullptr, Node* prev = nullptr) {
data_ = data;
next_ = next;
prev_ = prev;
}
};
Node* head_;
Node* tail_;
public:
class const_iterator {
protected:
Node* curr_;
public:
const_iterator(Node* p) {
curr_ = p;
}
.........
const_iterator operator--(int) {
const_iterator tmp = *this;
curr_ = curr_->prev_;
return tmp;
}
const T& operator*() const {
return curr_->data_;
}
const_iterator operator==(bool){
return false;
}
const_iterator operator!=(bool){
return true;
}
return;`
I need to fulfill the criteria below:
operator !=
returns true if two iterators point at different nodes, false otherwise
O(1)
I did not complete the logic of the operator, i just need to declare it properly so i dont get the error
The signature of your operator overloads are incorrect. You have them accepting a bool and returning an iterator. It should be the other way around.
Consider the operation you are performing
if(it1 == it2){
// do stuff
}
You even return a bool in your functions despite the signature requiring a iterator as return value.
Instead implement the operator overloads in the required signature
bool operator==(sorted_list_iterator it){
return (curr_->data_ == it.curr_->data_);
}
bool operator!=(sorted_list_iterator it){
return !(*this == it);
}
Note you can use your operator== overload in your operator!= to avoid repeating the equality logic in two functions. You may also be required to allow for a null curr_ in those functions.
I'm trying to write a template of list like std::list one.
This is my code in List.h:
#include <memory>
#include <cassert>
#include <iterator>
template<typename T, class Node>
class iterator : public std::iterator<std::bidirectional_iterator_tag, Node *, Node &> {
Node *underlying;
public:
explicit iterator(Node *n) : underlying(n) { };
iterator() : underlying(nullptr) { };
iterator &operator++() { //preinc
assert(underlying != nullptr && "Out-of-bounds iterator increment!");
underlying = underlying->next;
return *this;
}
iterator operator++(int) { //postinc
assert(underlying != nullptr && "Out-of-bounds iterator increment!");
iterator temp(*this);
++(*this);
return temp;
}
iterator &operator--() { //predec
assert(underlying != nullptr && "Out-of-bounds iterator decrement!");
underlying = underlying->previous;
return *this;
}
iterator operator--(int) { //postdec
assert(underlying != nullptr && "Out-of-bounds iterator decrement!");
iterator temp(*this);
--(*this);
return temp;
}
bool operator==(const iterator &rhs) {
return underlying == rhs.underlying;
}
bool operator!=(const iterator &rhs) {
return underlying != rhs.underlying;
}
T &operator*() {
return underlying->data;
}
};
template<typename T>
class List {
class Node {
public:
T data;
Node *previous;
Node *next; //is that T needed?
Node(T &d) : data(d) { };
};
private:
Node *head; //first element
Node *tail;
void create() { head = tail = NULL; }
void create(const List &rhs) {
iterator this_iter = head;
iterator rhs_iter = rhs.head;
while (rhs_iter != NULL) {
this_iter->data = (rhs_iter++)->data;
++this_iter;
}
};
public:
typedef T *iterator;
typedef const T *const_iterator;
typedef size_t size_type;
typedef T value_type;
List() { create(); };
List &operator=(const List &rhs) {
if (&rhs != this) {
create(rhs);
}
return *this;
};
List(const List &rhs) { create(rhs); };
~List() { while(head) remove(head); };
T *begin() { return head; };
T *end() { return tail; };
T front() { return head->data; };
T back() { return tail->data; };
bool empty() { return head == NULL; }
size_type size() {
size_t i = 0;
Node *node = head;
while (node) {
node = node->next;
i++;
}
return i;
};
T &operator[](size_type i) {
if (i < size() && i >= 0) {
Node *temp = head;
while (i > 0) {
temp = temp->next;
i--;
}
return temp->data;
}
throw std::out_of_range("Index out of range");
};
// const T &operator[](size_type i) const; //how to implement and do not duplicate code?
Node *push_back(value_type data) {
Node *n = new Node(data);
if (head == NULL) {
head = tail = n;
} else {
n->previous = tail;
tail->next = n;
tail = n;
}
return n;
};
Node *push_front(value_type data) {
Node *n = new Node(data);
if (head == NULL) {
head = tail = n;
} else {
n->next = head;
head->previous = n;
head = n;
}
return n;
};
void pop_front() {
remove(head);
};
void pop_back() {
remove(tail);
};
void remove(Node *n){
if(n == NULL) return;
if(n == head){
head = n->next;
head->previous =NULL;
}
else if(n == tail){
tail = n->previous;
tail->next = NULL;
}
else{
n->previous->next = n->next;
n->next->previous = n->previous;
}
delete n;
}
};
And this is main.cpp
#include <iostream>
#include "List.h"
int main(){
List<int> l;
l.push_back(1);
l.push_back(2);
l.push_back(3);
l.pop_back();
l.pop_front();
l.push_back(4);
l.push_back(5);
for (size_t i = 0; i < l.size(); i++)
std::cout << l[i] << "\n";
std::cout<<"Front "<<l.front();
std::cout<<"Back "<<l.back();
}
Actually push_back/front , pop_back/front and []operator work fine. But I get "Process finished with exit code 139"
error when I try use front() or back(). And I know that this iterator of list template does not work but I do know how to combine it up. Could anyone hint or help?
EDIT:
Ok, I have fixed the problem with removing and front(), tail() methods. But still the iterator thing doesnt work.
For example this code:
for(List<int>::iterator it = l.begin(); it!=l.end(); it++){
std::cout << it << "\n";
}
Gives me erros:
error: cannot convert ‘List<int>::Node*’ to ‘List<int>::iterator {aka int*}’ in initialization
for(List<int>::iterator it = l.begin(); it!=l.end(); it++){
^
error: comparison between distinct pointer types ‘List<int>::iterator {aka int*}’ and ‘List<int>::Node*’ lacks a cast [-fpermissive]
for(List<int>::iterator it = l.begin(); it!=l.end(); it++){
^
I know that te problem is with wraping the node with iterator template and that I have got "typename T *iterator".
Your begin and end methods return Node*, not your iterator type. And you made the iterator constructor that accepts Node* as an argument explicit; you told the compiler that implicit conversion from List<int>::Node* to List<int>::iterator is disallowed.
You must do one of:
Remove explicit from explicit iterator(Node *n) : underlying(n) { }; (though this risks implicit conversions in scenarios you don't want)
Change begin and end to return iterator(head) and iterator(tail) (performing explicit conversion to iterator) rather than head and tail (and change the return type of begin and end to iterator, which you should really do regardless)
You have some other issues too:
You should not have done typedef T* iterator in List; that hid the definition of your iterator class, so List never used it. Fixing that (and adding the necessary templating to your uses of iterator) makes it compile
The inheritance definition of iterator should be template<typename T, class Node> class iterator : public std::iterator<std::bidirectional_iterator_tag, T> not template<typename T, class Node> class iterator : public std::iterator<std::bidirectional_iterator_tag, Node *, Node &>; the latter is declaring the value from dereferencing should be Node * (when you want it to be T, the value in each Node)
end should return iterator<T, Node>(nullptr) not iterator(tail); otherwise, you stop before printing the value of tail, when you want to print tail before you terminate the loop.
Once all that is done, you should compile and get the results you expect. The code still has problems, e.g.
It's not const correct and offers no const versions of various accessors, which can prevent optimizations; you may end up recomputing size on every loop if the compiler can't figure out that the loop is actually non-mutating
The copy constructor/assignment utility function create doesn't work (you'd want to iterate over rhs and push_back repeatedly, you can't use an iterator to push on new values
The lack of const accessors means that utility function can't be made to work; it would need a const iterator type to iterate rhs with a guarantee that it would not violate the const List& requirement, but you only defined mutating iterator functions
But that it performance optimizations and correctness in code you're not exercising; you can fix that once you're satisfied with the new code.
I've your code (though the fix to copy construction/assignment is an egregious hack using const_cast to brute force around the lack of const safe iteration); I also added a couple tests to show that copy construction and assignment work. Take a look.
The problem (a problem?) is in remove(): you don't check if head is NULL (head case), if tail is NULL (tail case) and if n->previous and n->next are null (generic case)
I suggest this remove()
void remove(Node *n){
if(n == NULL) return;
if(n == head){
head = n->next;
if ( head )
head->previous =NULL;
}
else if(n == tail){
tail = n->previous;
if ( tail )
tail->next = NULL;
}
else{
if ( n->previous )
n->previous->next = n->next;
if ( n->next )
n->next->previous = n->previous;
}
delete n;
}