I need help with overloading '+' operator for adding together two doubly linked lists. I cannot compile my program due to getting "no match for operator=..." error. I have overloaded '=' operator already but struggle to print the result of the addition to std output. I have also overloaded the << operator. Been trying to figure out what is wrong for hours with no success. Any hints how to tackle this problem and/or solutions to it are very welcome. It is assignment for my OOP class.
Thanks in advance!
EDIT: The basic idea behind the code is to replicate set. Overloaded operators '+' should work as an union and '*' as intersection. I struggle to get the union properly printed to std output. '+=' seems to work fine. '<<' works good as well, but only when it comes to printing out single list.
EDIT:
Errors produced by compiler (g++, output from code::blocks, I have removed compiler notes):
llist3.cpp|149|error: no match for ‘operator=’ (operand types are ‘LList’ and ‘LList’)|
llist3.cpp|106|note: no known conversion for argument 1 from ‘LList’ to ‘LList&’|
llist3.cpp|151|error: no match for ‘operator=’ (operand types are ‘LList’ and ‘LList’)|
llist3.cpp|106|note: no known conversion for argument 1 from ‘LList’ to ‘LList&’|
llist3.cpp|152|error: no match for ‘operator<<’ (operand types are ‘std::ostream {aka std::basic_ostream<char>}’ and ‘LList’)|
#include<iostream>
using namespace std;
class LList {
public:
struct Node {
int elem;
Node* succ;
Node* prev;
Node() : succ(0), prev(0), elem(0) {}
};
LList();
LList(LList& list);
~LList();
Node* next();
Node* begin() { curr = head; }
int getElem() { return curr->elem; }
void addElem(int elem);
LList operator+(LList& set);
LList operator+(int elem);
LList& operator+=(LList& set);
LList& operator+=(int elem);
LList& operator=(LList& list);
friend ostream& operator<<(ostream& os, LList& obj);
private:
Node* curr;
Node* head;
Node* tail;
int size;
void pushFront(Node* n);
void pushInside(Node* n);
void pushBack(Node* n);
};
LList::LList() : head(0), tail(0), size(0), curr(0) {}
LList::LList(LList& list) : size(0), curr(0), head(0), tail(0) {
list.curr = list.head;
while(list.curr) {
addElem(list.getElem());
list.next();
}
}
LList::Node* LList::next() {
if (curr)
return (curr = curr->succ);
else
return 0;
}
void LList::addElem(int elem) {
Node* n = new Node;
n->elem = elem;
if (curr) {
if (curr == head && elem < curr->elem) {
pushFront(n);
}
else if (elem > curr->elem) {
curr = curr->succ;
addElem(elem);
}
else if (elem < curr->elem && elem > (curr->prev)->elem) {
pushInside(n);
}
else if (elem < curr->elem) {
curr = curr->prev;
addElem(elem);
}
} else {
pushBack(n);
}
}
void LList::pushFront(Node* n) {
head = n;
n->succ = curr;
curr->prev = n;
n->prev = 0;
curr = n;
size++;
}
void LList::pushInside(Node* n) {
(curr->prev)->succ = n;
n->succ = curr;
n->prev = curr->prev;
curr->prev = n;
size++;
}
void LList::pushBack(Node* n) {
if (!head) {
head = n;
} else {
tail->succ = n;
n->prev = tail;
}
tail = n;
curr = n;
size++;
}
LList::~LList() {
for (curr = head; curr;) {
Node* temp = curr->succ;
delete curr;
curr = temp;
}
}
LList& LList::operator=(LList& list) {
list.begin();
if (this != &list) {
for (curr = head; curr;) {
Node* temp = curr->succ;
delete curr;
curr = temp;
}
while (list.curr) {
addElem(list.getElem());
list.next();
}
}
return *this;
}
ostream& operator<<(ostream& os, LList& list) {
LList::Node* p = list.head;
os << "{ ";
while(p) {
os << p->elem << (p->succ ? ", " : "");
p = p->succ;
}
os << " }" << endl;
return os;
}
LList LList::operator+(LList& set) {
LList temp = *this;
temp += set;
return temp;
}
LList LList::operator+(int elem) {
*this += elem;
return *this;
}
int main() {
LList setA;
setA.addElem(1234);
setA.addElem(1435);
setA.addElem(1100);
LList setB;
setB.addElem(1234);
setB.addElem(1435);
setB.addElem(5100);
setB = setA + 1234; // 1st error here
LList setD;
setD = setA + setB; //2nd
cout << setA + setB << endl; //3rd
}
There is one glaring error in your code:
Node* begin() { curr = head; }
This code invokes undefined behavior, since you are not returning a value. It should be this:
Node* begin() { curr = head; return curr; }
In addition, you should pass your LList by const reference in functions that do not change the LList parameter:
For example:
LList::LList(LList& list);
LList& operator=(LList& list);
friend ostream& operator<<(ostream& os, LList& obj);
should be:
LList::LList(const LList& list);
LList& operator=(const LList& list);
friend ostream& operator<<(ostream& os, const LList& obj);
Please change these and the other functions to pass const references. If you want to see why you should change this, you will see the issue immediately if you tried to do this:
LList list1;
LList list2;
//...
std::cout << list1 + list2;
The operator << is looking for non-const LList objects, but the addition "inline" returns a temporary LList (which will mean that the return value will be const). The code will not compile due to your overloaded operator << accepting only non-const LList.
So you need to change your parameter in operator << to a const LList&.
You have a built in "current" pointer in your list class. This is a grave design error. You are unable to define your functions correctly because of this error.
It is a design error because with this design you cannot iterate over const lists, and this means, among other bad things, that you cannot do anything useful with temporary lists. So when you calculate setA + setB, you cannot assign it to anything,because to assign you need to iterate, so you need a non-const argument to operator= and to the copy constructor. But you cannot bind a temporary to a non-const reference.
Even if you bypass the public interface in the copy constructor and the copy assignment operator, and copy the list directly without using curr, you will have the same problem with any user function that must use the public interface. That is, setA + setB will not be usable as a function argument. You will need to assign it to some variable first, and then pass that variable to the function.
You also cannot take a list and pass it down to some function in the middle of an iteration, and expect to continue to iterate from the place you've left, because anything that iterates the list changes the curr pointer.
The best solution is to get rid of the curr member and make most of your arguments const LList&. While this is not the onky solution, there are many other drawbacks to having a current pointer built into a list class so I won't talk about them.
In order to iterate the list, you have to supply a separate object that can go back and forth over the list, and a separate variant of it that can go back and forth over a const list. This is called an iterator and you need to read up on this concept if you want to do anything in C++.
In your case iterators can be Node* and const Node*. You only need to provide member functions that return the first node in the list, which you already have. Real libraries do the same. They normally wrap the node pointer in a separate iterator class, for various reasons; but for a simple homework this is not necessary (though you can do it if you want).
Related
In the below code we try to multiply each element's data in the list by 2 and assign it. But the apply function is a const function therefore should not be able to change the values of member fields. Output for the fifth line in main is
6
4
2
2
4
So code below succeeds in changing the values as intended and I can't figure out why.
#include <iostream>
#include <list>
#include <string>
using std::ostream;
using std::cout;
using std::endl;
template<class E> class MyList {
class Node {
friend class MyList<E>;
E data;
Node* next = nullptr;
}; // end of class Node
Node* head = new Node;
Node* tail = head;
MyList(const MyList&) = default;
public:
MyList() = default;
MyList& operator=(const MyList&) = delete;
void push_front(const E& data) {
Node* node = new Node;
node->data = data;
node->next = head->next;
head->next = node;
if(head->next == nullptr) tail = node;
}
void push_back(const E& data) {
if(head->next == nullptr) {
push_front(data); return;
}
MyList temp(*this);
temp.head = temp.head->next;
temp.push_back(data);
temp.head = nullptr;
}
~MyList() {
Node *node = head, *next;
while(node != nullptr) {
next = node->next;
delete node;
node = next;
}
}
template<class Function>
void apply (Function f) const {
Node* node = head->next;
while(node != nullptr) {
f(node->data);
node = node->next;
}
}
};
int main() {
MyList<int> m1;
m1.push_back(3);
for(int i = 1; i <= 2; ++i) m1.push_front(i);
for(int i = 1; i <= 2; ++i) m1.push_back(i);
m1.apply(
[](auto& val){ val *= 2;}
);
m1.apply(
[](const auto& val){cout << val << endl;}
);
return 0;
}
The key is logical vs bitwise constness. The head data member is a non-const pointer to non-const Node: the const correctness of the apply member function is bitwise constness:
you cannot change what the head data member (pointer) points to from a const-qualified member function.
You can, however, mutate the Node object that it points to.
Because it is not the same, the pointer you store in your const struct as a member, than the data pointed by that pointer, which still is of type Node* (non const).
Just for the sake of showing, try to set all the pointers to NULL:
while(node != nullptr) {
f(node); // Pass the Node*, instead of the reference to the value
node = node->next;
}
The above code will not compile, as you will be passing a Node* const& to your lambda, which will be a const reference and will not be possible to set it to NULL.
The const in the functions declarations is not transitive. The pointed memory accessed through some pointer member is not affected by that. It will still have the same type as that of the declared pointer member, in this case Node*.
This is called (as stated in the other answer) logical constness, and is ensured by the function const signature.
While you are trying to achieve bitwise constness, which is not achieved with the signature, but with the type declaration.
And about types, inside the const function the type of the pointer would become Node* const, meaning a constant pointer (logical constness, cannot change where it points), while the type of the data pointed is Node* (it can be changed, bitwise constness)
I'm trying to write a function for the deletion of a node in a linked list , given a double pointer to the head and a pointer to the node to be deleted. (the node to be deleted will not be the tail node)
this is what I have tried:-
public:
int value;
Node* next;
};
/*
headPtr is a reference to the head node(i.e. pointer to pointer) and
deleteNodePtr is the node which is to be deleted. You can see the Node definition above.
It is guaranteed that deleteNodePtr will not point to the last element.
*/
void deleteNode(Node** headPtr, Node* deleteNodePtr) {
Node* current;
current=*headPtr;
if (*headPtr==deleteNodePtr){
*headPtr=deleteNodePtr->next;
//delete current;
return;
}
else {
Node* prev = current;
while(current->next!=deleteNodePtr){
prev = current;
current=current->next;
}
prev->next =current->next;
//delete current;
return;
}
return;
}
I can see multiple things:
1 - In your while condition you are not checking that current is valid:
while(current->next!=deleteNodePtr){
prev = current;
current=current->next;
}
2- What probably is happening to you now: the deleteNodePtr points to the second element in the list, so you never enter to the while loop which means the prev == current which means that you are assigning current->next = current->next.
A solution for this would be:
void deleteNode(Node** headPtr, Node* deleteNodePtr) {
Node* current;
current = *headPtr;
if (current == deleteNodePtr) {
current = deleteNodePtr->next;
delete current;
}
else {
Node* prev = current;
current = current->next;
while (current!= nullptr && current != deleteNodePtr) {
prev = current;
current = current->next;
}
if(current!=nullptr)
{
prev->next = current->next;
delete current;
}
}
return;
}
3- You never checks if headPtr is valid, it couldn't be.
4- It's more a suggestion in the stylish thing rather than a code problem, so if you don't share this point of view, you can freely ignore it. At the very begin you assign *headPtr to current, but instead using current for further usage, you use headPtr. It would be much clear instead, if you always use current:
Original:
Node* current;
current=*headPtr;
if (*headPtr==deleteNodePtr){
*headPtr=deleteNodePtr->next;
//delete current;
return;
}
Suggestion:
Node* current;
current=*headPtr;
if (current==deleteNodePtr){
current=deleteNodePtr->next;
//delete current;
return;
}
This appears at first blush to be a C-style linked list written in C++. It's hard to say for sure since we don't have a bigger picture of your code. A C++ linked list would at least put these functions in a class, and not have a global Node.
The node is what's called an implementation detail. It helps us write the list, but users of the list class should not be able to declare their own nodes, nor should they be aware that nodes exist. Hopefully people using your class aren't calling this delete function explicitly.
Users of your List class might/should (depends) prefer to have iterators available to them. If anything writing iterators for your containers will allow them to be used with range-based for loops. Since your linked list appears to be singly linked, your iterators can only move forward. I also wrote my class with an iterator as I imagine it will help prevent copy/paste homework submissions in most cases.
Now to your function. Per my comment, passing the head as a double pointer makes no sense. Not once do you use it as a double pointer; you always de-reference it. So cut out the middle-man and pass it a node pointer from the get-go.
There's a case that's guaranteed not to happen, and that's that deleteNodePtr will never be the last node. That sounds bad. People can want to delete the last node, but they're allowed and no justification is given.
There's no code to catch an attempt to delete on an empty list.
Your specific issue seems to lie here:
while(current->next!=deleteNodePtr){
prev = current;
current=current->next;
}
You break out of the loop when current->next is the node to be deleted, and not current. You end up deleting the wrong node (current, which is 1 before deleteNodePtr), and that's likely going to cause issues down the line. For instance, if the second node is supposed to be deleted, you end up deleting your head, and that breaks stuff. I imagine that removing the ->next from your Boolean condition would fix the issue. I can't provide a deeper resolution without seeing more of your code.
=== Optional reading ===
Here's an extremely stripped down linked list written as a C++ class. You can run this code here: https://godbolt.org/z/7jnvje
Or compile it yourself.
#include <iostream>
// A simple linked list for integers
class List {
public:
List() = default;
~List();
// List(const List& other); // Copy ctor needed for Rule of 5
// List(List&& other) noexcept; // Move ctor needed for Rule of 5
void push_back(int val);
class iterator;
iterator begin();
iterator end();
iterator find(int val);
void erase(iterator it);
void clear();
// friend void swap(List& lhs, List& rhs); // Not Rule of 5; aids Rule of 5
// List& operator=(List other); // Assignment operator needed for Rule of 5
/*
* Quick note on Rule of 5 functions.
* If your class deals with heap-allocated resources, certain functions become
* required. The only one I included was the destructor. The signature of my
* assignment operator is different than you might see, but the reason is
* it's written to take advantage of the copy/swap idiom.
* https://stackoverflow.com/a/3279550/6119582
*/
private:
// Data
struct Node {
int value = 0;
Node *next = nullptr;
Node(int val) : value(val) {}
};
Node *m_head = nullptr;
Node *m_tail = nullptr;
// Functions
Node *find_node(int val);
};
class List::iterator {
public:
iterator(Node *loc) : location(loc) {}
iterator &operator++();
int operator*();
bool operator==(const List::iterator &rhs);
bool operator!=(const List::iterator &rhs);
private:
Node *location;
};
// List Implementation
List::~List() { clear(); }
void List::push_back(int val) {
if (m_tail) {
m_tail->next = new Node(val);
m_tail = m_tail->next;
return;
}
m_head = new Node(val);
m_tail = m_head;
return;
}
List::iterator List::begin() { return iterator(m_head); }
List::iterator List::end() { return iterator(nullptr); }
List::iterator List::find(int val) { return iterator(find_node(val)); }
void List::erase(iterator it) {
// Emtpy list or end()
if (!m_head || it == end())
return;
Node *toDelete = find_node(*it);
// Deleting head
if (toDelete == m_head) {
m_head = m_head->next;
delete toDelete;
return;
}
// Deleting tail
if (toDelete == m_tail) {
Node *walker = m_head;
while (walker->next != m_tail) {
walker = walker->next;
}
m_tail = walker;
delete m_tail->next;
m_tail->next = nullptr;
return;
}
// Delete any middle node; by moving value until it is the tail, then
// deleting the tail
while (toDelete->next) {
toDelete->value = toDelete->next->value;
if (toDelete->next == m_tail) {
m_tail = toDelete;
}
toDelete = toDelete->next;
}
delete toDelete;
m_tail->next = nullptr;
}
void List::clear() {
while (m_head) {
Node *tmp = m_head;
m_head = m_head->next;
delete tmp;
}
m_tail = nullptr;
}
List::Node *List::find_node(int val) {
if (!m_head) {
return nullptr;
}
Node *walker = m_head;
while (walker && walker->value != val) {
walker = walker->next;
}
return walker;
}
// List iterator implementation
List::iterator &List::iterator::operator++() {
location = location->next;
return *this;
}
int List::iterator::operator*() { return location->value; }
bool List::iterator::operator==(const List::iterator &rhs) {
return location == rhs.location;
}
bool List::iterator::operator!=(const List::iterator &rhs) {
return !(*this == rhs);
}
// Free function
// NOTE: Should take list by const reference, but I didn't add the necessary
// code for that. I'm not passing by value because I also left out Rule of 5
// code that is otherwise required.
// NOTE 2: Could also be templatized and made more generic to print any
// container, but that's outside the scope of this answer.
void print(List &list) {
for (auto i : list) {
std::cout << i << ' ';
}
std::cout << '\n';
}
int main() {
List list;
for (int i = 1; i <= 10; ++i) {
list.push_back(i);
}
print(list);
list.erase(list.find(1));
print(list);
list.erase(list.find(10));
print(list);
list.erase(list.find(6));
print(list);
auto it = list.begin();
for (int i = 0; i < 3; ++i) {
++it;
}
list.erase(it);
print(list);
list.erase(list.find(25)); // Bogus value; could throw if so desired
print(list);
}
Erasing is made much easier with a doubly-linked list, but we don't have one. My erase function makes some checks and handles the head and tail situations individually. For any node in the middle of the list, I don't bother deleting that node specifically. What I do instead is shuffle the value to be deleted to the tail of the list, and then delete the tail.
My comments indicate some things that were left out. I also didn't mark any functions as const. My iterator does not satisfy all requirements of a ForwardIterator. People can probably find other things I left out. I have a couple reasons for this. Mainly that this is quick and dirty code, and I prefer to not provide the temptation of a copy/paste solution.
It would be nice if all C++ instructors would actually teach C++, though. This form of linked list should not be taught in a C++ class anymore.
So i have a Linked list implementation of my own and it can successfully keep integers and call them when needed with overloaded [] operator but when it comes to storing a class in my linked list, it seems that i can't call the class appropriately (using the same [] operator).
Called functions and members of my Linked List;
#include <iostream>
#include <assert.h>
template<typename T>
struct node {
T data;
node<T>* next;
};
template<typename T>
class Vectem {
private:
node<T>* head;
node<T>* last;
int lenght;
public:
void insert(T value) {
last->next = new node<T>;
last = last->next;
last->data = value;
last->next = NULL;
if (isEmpty()) {
head = last;
}
lenght++;
}
node<T>* search(int indx) {
node<T>* current;
current = head;
int count=0;
while (current != NULL) {
if (count == indx) {
break;
}
current = current->next;
count++;
}
return current;
}
T& operator [](int indx) {
assert(indx >= lenght - 1);
T result;
result = search(indx)->data;
return result;
}
};
And here is the main function and the class that i try to store;
#include <iostream>
#include <fstream>
#include <string>
#include "VectemLibrary.h"
class word {
public:
std::string value;
int count;
word(std::string value, int count): value(value),count(count) {
}
word() {
value = "NOT ASSIGNED";
count = 0;
}
word(const word& w1) {
value = w1.value;
count = w1.count;
}
~word() {
std::cout << "Word Destroyed" << std::endl;
}
};
int main()
{
Vectem<word> wordContainer;
word newWord("hello", 1);
wordContainer.insert(newWord);
std::cout << wordContainer[0].value;
}
Visual studio gave me the expection with this message at the last line where i call the first member of linked list with [];
Exception thrown at 0x7A0CF3BE (ucrtbased.dll) in Top 10 words.exe: 0xC0000005: Access violation reading location 0xCCCCCCCC.
I think that my lack of experience with pointers may have caused the problem but if you see something that i can't, Please enlighten me.
There are other problems with the code you posted as well (e.g. isEmpty() is not declared or defined), but I'll focus on the issue you explicitly mentioned.
In your operator:
T& operator [](int indx) {
assert(indx >= lenght - 1);
// You declare this variable on the stack
T result;
result = search(indx)->data;
// And then you return this variable by reference; this is not okay
return result;
}
As mentioned in my code comments (and by #Johnny Mopp in his comment to your post), you shouldn't (can't) return a reference or pointer to a variable declared within the returning function and constructed on the stack. Anything on the stack will be destroyed once the function call ends, so any returned pointers or references to such variables will be dangling references; using said pointers or references will result in undefined behavior.
So you don't want to return a reference to a stack-allocated variable like result; you want to return a reference to the data within the node itself (which is allocated on the heap by insert()), as it will still be a valid reference after the function returns:
return search(indx)->data;
There are several problems with your code, but the most important is that you are not initializing the head, last, or lenght members of Vectem at all. An Access Violation error at address 0xCCCCCCCC is a good indication that uninitialized memory is being accessed, as some compilers/setups fill uninitialized memory with 0xCC bytes, thus head and last are initially 0xCCCCCCCC in your case.
You need to add appropriate constructors to Vectem (as well as a destructor, a copy constructor, and a copy assignment operator, per the Rule of 3), eg:
template<typename T>
class Vectem {
private:
node<T>* head;
node<T>* last;
int lenght;
public:
Vectem() : head(NULL), last(NULL), lenght(0) {}
Vectem(const Vectem &src) : head(NULL), last(NULL), lenght(0)
{
// copy src's data to *this as needed ...
}
~Vectem()
{
// cleanup *this as needed ...
}
Vectem& operator=(const Vectem &rhs)
{
if (&rhs != this) {
// clear *this, and copy rhs's data to *this, as needed ...
}
return *this;
}
...
};
Or, in C++11 and later, you can initialize the members directly in their declarations (also, be sure to add a move constructor and a move assignment operator, per the Rule of 5), eg:
template<typename T>
class Vectem {
private:
node<T>* head = nullptr;
node<T>* last = nullptr;
int lenght = 0;
public:
Vectem() = default;
Vectem(const Vectem &src)
{
// copy src's data to *this as needed ...
}
Vectem(Vectem &&src) : head(src.head), last(src.last), lenght(src.lenght)
{
src.head = nullptr;
src.last = nullptr;
src.lenght = 0;
}
~Vectem()
{
// cleanup *this as needed ...
}
Vectem& operator=(const Vectem &rhs)
{
if (&rhs != this) {
// clear *this, and copy rhs's data to *this, as needed ...
}
return *this;
}
Vectem& operator=(Vectem &&rhs)
{
// clear *this as needed...
head = rhs.head; rhs.head = nullptr;
last = rhs.last; rhs.last = nullptr;
lenght = rhs.lenght; rhs.lenght = 0;
return *this;
}
...
};
That being said, insert() is also buggy, as it is dereferencing last before checking that last is actually pointing at a valid node. Try something more like this instead:
void insert(T value) {
node<T> *n = new node<T>{value, NULL};
if (!head) head = n;
if (last) last->next = n;
last = n;
++lenght;
}
Alternatively:
void insert(T value) {
node<T> **p = (last) ? &(last->next) : &head;
*p = new node<T>{value, NULL};
last = *p;
++lenght;
}
This is my LinkedList namespace
namespace LinkedList {
template<class T>
class Node{
public:
Node(const T& data)
:data_(data), next_(nullptr) {}
T data_;
Node<T> *next_;
Node<T> *operator++(){
return next_;
}
};
template<class T>
class LinkedList{
public:
LinkedList()
: head_(nullptr), tail_(nullptr) {}
~LinkedList(){
Node<T> *curr = head_;
Node<T> *next;
while(curr != nullptr){
next = curr->next_;
delete curr;
curr = next;
}
}
void Append(const T& data) {
Node<T> *tmp = new Node<T>(data);
if(head_ == nullptr) {
head_ = tmp;
tail_ = tmp;
} else if(head_ == tail_){
head_->next_ = tmp;
tail_ = tmp;
} else {
tail_->next_ = tmp;
tail_ = tmp;
}
}
void Present(){
for(Node<T> *curr = head_; curr != nullptr; curr=curr->next_){
std::cout << curr->data_ << std::endl;
}
}
Node<T> *begin(){
return head_;
}
Node<T> *end(){
return nullptr;
}
private:
Node<T> *head_;
Node<T> *tail_;
};
}
I was reading into Iterators and wanted to make my LinkedList object compatible with range based for loops. This is what I got from reading about range-based for loops
for(auto x: list){ }
is equivalent to
for(; begin != end; ++begin) { //*begin};
I thought I was being cheeky and could skip a couple steps of operator overloading (!=, *) for my LinkedList to work with range based for loops by programming my iterator into my Node class for no other reason besides that I felt like it. This is only my second day of learning C++ so this might be a stupid question but I am still a little confused on why this doesn't work:
LinkedList::LinkedList<int> list;
list.Append(3);
list.Append(5);
for(auto x: list) { //blah blah blah}
I know that my prefix increment is the core issue, when the for loop does ++__begin, what I am hoping it does is ___begin=_begin->next however it does not. Why is this? How the operator overloading works in my mind is is that whatever member variables I reference in the overloading function, it is referencing from the instance I am operating on. And when I return the ptr, it is setting whatever instance I am operating on to this ptr. I know my understanding of this is wrong because it doesn't work, so please someone explain to me how it actually works lol.
You've written a
template<class T>
Node<T>* Node<T>::operator++();
which would get invoked like
Node<int> n;
Node<int> *p = ++n; // calls the pre-increment operator
Note this is quite different to the canonical form
Node<T>& Node<T>::operator++();
which returns a reference to the incremented object that you can use in an expression.
But there's another problem: operator overloading works
When an operator appears in an expression, and at least one of its operands has a class type or an enumeration type, ...
But Node<T>* is not a class type. It's a pointer, and pointers already have their own built-in operators. You can't overload them. There is no Node<T>*::operator++().
Write an iterator. It's not that bad, and they actually work!
NB. Iterators are designed to generalize pointers, and to have a compatible interface. You can use raw pointers if your container is basically an array - because incrementing a pointer is already the right way to iterate over an array.
It's only when we want to provide array-like or pointer-like access to a non-contiguous structure that we have to do this extra work.
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;
}