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)
Related
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 don't fully understand the concept of templates and am trying to get some help on how to implement one on my linked list below. I'm trying to get my code to be able to support the following types : List< List<std::string> > List<std::string> List<int>. I was wondering if there was any way someone could give me an example of how to convert these items into templates in addition to trying to explain what is happening? I'm new to c++ so any help I can get would be appreciated.
#include <string>
#include <iostream>
#include <cstddef>
using Item = std::string;
// TURN DList into a template!
class DList {
private:
class DListNode {
public:
Item item;
DListNode * next;
DListNode * prev;
DListNode(Item i, DListNode *n=nullptr, DListNode *p=nullptr) {
item = i;
next = n;
prev = p;
}
};
DListNode * head;
DListNode * tail;
public:
class iterator {
DListNode *node;
public:
iterator(DListNode *n = nullptr) {
node = n;
}
Item& getItem() { return node->item; }
void next() { node = node->next; }
void prev() { node = node->prev; }
bool end() { return node==nullptr; }
friend class DList;
};
public:
DList() {
// list is empty
head = nullptr;
tail = nullptr;
}
bool empty() {
return head==nullptr;
}
void append(Item a) {
DListNode *node = new DListNode(a,nullptr,tail);
if ( head == nullptr ) {
// empty list
head = node;
tail = node;
} else {
tail->next = node;
tail = node;
}
}
void insertAfter(iterator it, Item item)
{
if(head == nullptr || it.node == nullptr) { // NULL iterator means insert at head
DListNode *node = new DListNode(item,head); // next=head, prev=NULL
if ( head == nullptr) // same as zyBook
head = tail = node;
else { // if inserting before head, it.node==NULL
head->prev = node;
head = node;
}
} else if (it.node == tail) {
DListNode *node = new DListNode(item,nullptr,tail); // next=NULL, prev=old tail
tail->next = node;
tail = node;
} else {
DListNode *node = new DListNode(item,it.node->next,it.node);
it.node->next = node;
node->next->prev = node;
}
}
void erase (iterator it) {
DListNode *succ = it.node->next; // successor node
DListNode *pred = it.node->prev; // predecessor node
if (succ != NULL)
succ->prev = pred;
if (pred != NULL)
pred->next = succ;
if (it.node == head)
head = succ; // head is following node
if (it.node == tail)
tail = pred; // tail is previous node
delete it.node; // delete the node; not shown in zyBook, but necessary in C/C++
// iterator is now invalid, caller should not use it again
}
iterator begin() {
return iterator(head);
}
iterator reverse_begin() {
return iterator(tail);
}
};
template <typename Item>
std::ostream& operator << (std::ostream& out, DList<Item> &l)
{
out << "{";
auto it = l.begin();
out << it.getItem();
it.next();
for(; !it.end(); it.next())
{
out << ", " << it.getItem();
}
out << "}" << std::endl;
return out;
}
int main()
{
{
DList<std::string> l;
l.append("eggs");
l.append("milk");
l.append("bread");
std::cout << l;
}
{
DList<int> l;
l.append(1);
l.append(2);
l.append(3);
std::cout << l;
}
return 0;
}
Actually, you almost have all you need, but you are still using a regualar class with a concrete type.
using Item = std::string;
class DList { ... };
So first we drop the concrete type:
// using Item = std::string;
class DList { ... }; // sure Item is now undefined...
Then we tell the class to be a template
template <typename Item>
class DList { ... };
Now Item got re-introduced, but instead of being a concrete type, it's now a generic one. That's it, you have a template list (assuming the list is implemented correctly, I didn't check).
Whenever you now instantiate your list:
DList<int>;
DList<std::string>;
// ...
You create a totally new, independent data type (which means especially, that you cannot assign a DList<int> to a pointer to DList<double>, just all alike as you cannot assign a int to a pointer to double either).
When you instantiate a template, every occurence of a template parameter will be replaced with the type you instantiated the template with, e. g. in DList<int>, every occurence of Item will be replaced with int.
Well, all this is just a very short introduction, there's quite a lot to follow yet, but that's rather to be handled in book than in an answer on stackoverflow...
Some notes to your node's constructor, though:
DListNode(Item i /* , ... */) { item = i; }
At very first, you should get used to using constructor's initialiser list (not to be confused with std::initializer_list):
DListNode(Item i /* , ... */) : item(i) { }
You avoid default initiasation + assignment in favour of direct initialisation by value. Additionally, some types (non-default constructible ones, const members and references) only can be initialised that way.
Then you are producing an unnecessary copy:
DListNode(Item i /* , ... */) : item(i) { }
// ^ temporary copy ^ final copy, created from temporary
You avoid that copy, if you accept the item by reference:
DListNode(Item const& i /* , ... */) : item(i) { }
// now copies from reference, one copy less
You can additionally provide move semantics:
DListNode(Item&& i /* , ... */) : item(std::move(i)) { }
so that objects you don't need outside the list any more can be moved into (well, actually their contents). In some cases, this can be much cheaper than a full copy...
All said about the constructor (apart from the initialiser list) applies to the append and insertAfter functions as well.
Initialiser lists and avoiding copies is general advice, unrelated to templates...
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).
Suppose I have the following definition of List and Node:
template <class T>
class List {
public:
class Iterator;
class ConstIterator;
//Constructors and Destructors.
List() : head(NULL), tail(NULL), size(0) {}
List(const List& list);
~List();
//Methods
Iterator begin();
ConstIterator begin() const;
Iterator end();
ConstIterator end() const;
void insert(const T& data);
void insert(const T& data, const Iterator& iterator);
void remove(const Iterator& iterator);
int getSize() const;
Iterator find(const T& item);
ConstIterator find(const T& item) const;
void sort();
//Operators
List operator = (const List& list);
private:
class Node;
Node* head;
Node* tail;
int size;
};
template <class T>
class List<T>::Node
{
public:
//Constructors and destructors
Node(const T& _data, const Node* _next) : data(_data), next(_next) {}
~Node(); //Destructor
//Methods
//Operators
Node operator = (const Node& node);
private:
T data;
Node* next;
};
I'm writing a function to insert data into a list like this:
template<class T>
void List<T>::insert(const T& data)
{
Node newNode = new Node(data, NULL);
if (head == NULL)
{
head = &newNode;
tail = &newNode;
}
else
{
(*tail)->next = &newNode;
tail = &newNode;
}
size++;
}
However what I find strange is that if I swap (*tail)->next = &newNode; to (*tail).next = &newNode; it still compiles. Why, and what is the correct way of doing it?
The definitions of your classes can be (for the purposes of this question) simplified into:
class List {
...
private:
Node* head;
Node* tail;
};
class Node {
...
private:
Node* next;
};
Now in your List::insert method:
Node newNode = new Node(data, NULL);
(*tail)->next = &newNode;
...when you use new expression, the result will be pointer to the newly allocated memory.
What you should do is:
Node* newNode = new Node(data, NULL);
tail->next = newNode; // <-- equivalent to (*tail).next = newNode;
Using Node->tail is short form of writing (*Node).tail. Both forms are valid. Strangeus is the fact that you say that (*Node)->tail compiles. To this happens, Node must be defined as a double pointer, i.e.:
Node **tail;
But your code has some others bugs in. In this line:
Node newNode = new Node(data, NULL);
you are define a local object and assing a dynamic memory to it. The correct way is:
Node *newNode = new Node(data, NULL); // defining it as a pointer
and instead of assing as:
head = &newNode;
do:
head = newNode;
As a final note, consider using smart pointer instead of raw pointer. The former is safer than the last
The -> operator will automatically derefference a pointer for you then call the method to the right. So:
tail->next
would also work but
tail.next
wouldn't because tail is a pointer. To use the . operator you have to defrence the pointer first as in
(*tail).next
(*tail)
turns your pointer into an object. At that point you can use either -> or .
A . will not work on a pointer but -> will.
Generally, just for easy of typing I use -> because it is shorter then using (*) to turn a pointer into an object just so I can use a dot but they are equivalent operations.
You have noticed that (*tail)->next = &newNode and (*tail).next = &newNode both compile, which strikes you as odd.
But somehow you might also have noticed that this line also compiles!
Node newNode = new Node(data, NULL);
That is the thing that you should give you pause.
You are inside of a template here. Lots of things "compile".
Did you try instantiating the template?
ADDENDUM:
Here just to show you how crazy things can be, check out this program:
#include <iostream>
using namespace std;
template <class T>
class C {
void f();
};
template <class T>
void C<T>::f() {
int x = new int;
}
int main() {
std::cout << "Hello, world\n";
}
Now check this out:
$ g++ template-example.cpp && ./a.out
Hello, world
But now notice
#include <iostream>
using namespace std;
int main() {
int x = new int;
std::cout << "Hello, world\n";
}
which yields:
$ g++ hello.cpp
hello.cpp: In function ‘int main()’:
hello.cpp:4: error: invalid conversion from ‘int*’ to ‘int’
TL;DR: WHEN YOU ARE IN A TEMPLATE, THINGS THAT SHOULD NOT COMPILE SOMETIMES "DO"! (They're not really compiling -- YET!)