Brandon
2012-05-17 16:53:57
[LEFT]Notes:
I suck at making tutorials but I thought I'd try it.. I also don't know how to make the forum preserve white space :S It keeps clustering everything so close when infact it looks far different in editing mode. Also not sure why the font is so small (Times New Roman) but I hope you guys can see it clearly. Hopefully your eyes aren't too bad. :S
Finally Feel Free to edit it however you want. That means you can edit this thread if your a mod or something or if you know better. Or you can just leave a comment and I can edit it for you. Doesn't matter.
Description:
This tutorial should teach you how to port pascal types to C++ or any other language in fact. It allows you to create your own types and objects and allow for infinite parameters! Good luck to all reading. Hope I made it all clear.
TABLE OF CONTENTS:
[/LEFT]
I suck at making tutorials but I thought I'd try it.. I also don't know how to make the forum preserve white space :S It keeps clustering everything so close when infact it looks far different in editing mode. Also not sure why the font is so small (Times New Roman) but I hope you guys can see it clearly. Hopefully your eyes aren't too bad. :S
Finally Feel Free to edit it however you want. That means you can edit this thread if your a mod or something or if you know better. Or you can just leave a comment and I can edit it for you. Doesn't matter.
Description:
This tutorial should teach you how to port pascal types to C++ or any other language in fact. It allows you to create your own types and objects and allow for infinite parameters! Good luck to all reading. Hope I made it all clear.
TABLE OF CONTENTS:
- Header Guards & The Pre-Processor.
- Includes & Namespaces.
- Templates & Classes.
- Class Members & Inheritance.
- Recursive Functions & Stack Overflows.
- Templates & The Ellipsis Operator.
- Overloading Operators: Subscript.
- Overloading Operators: Comparison.
- Overloading Operators: Assignment.
- Overloading Operators: Insertion.
- Overloading Operators: Parameters.
- Overloading Operators: Insertion -> Outstream.
- Usage & Examples!
- Full Class: The End.
- Extras: .Net Framework Version.
[/LEFT]
Header Guards & The Pre-Processor:
Pre-Processor directives begin with the Hash sign (#). This allows us to communicate directly with our compiler and tell it what to do. In simba we use {$DEFINE …….}.
#ifndef is a pre-processor directive that checks to see if something is NOT already defined. If it is not defined, then we define it using #define.
#endif is the final directive that we will be using. It is used to tell the compiler that the definition here is finished. Basically like Begin and End in simba. You do not want missing brackets or missing begin's and end's all over the place.
Header Guards use these so that they aren't included TWICE! You never want to include the same code over and over and over. A header guard prevents this for you. If it's already included then just ignore it right? Good. So the below code is the definition of our header guard. It checks to see if our Header is already included and if it is NOT then include it.
#ifndef ARRAYOFANYTHING_H_INCLUDED
#define ARRAYOFANYTHING_H_INCLUDED
//All our code, even our includes will go here. It HAS to go BETWEEN the header guards to be protected from being included more than once!
#endif Includes & Namespaces:
First we shall include everything we ever need to use this class:
#include <iostream> //We include this for dealing with input/output/streams aka io-stream
#include <vector> //This is so we can use our vector.
using namespace std; //We use this so we don't have to type std::vector or std::anything, etc.. Std is a namespace.Templates & Classes:
Finally we create a Templated class that takes any parameter. AKA T. T will represent "Anything". Our class will be an Array of "Anything" aka an array of T.
template<typename T> //We create a templated class. This is a class that can be constructed with any type.
class ArrayOfAnything //The name of our class.. Just name it anything. I chose this name for tutorial purposes.
Class Members & Inheritance:
So now we start creating members. Our members can be private meaning it can only be accessed by other members of the class. Thus a user of our class cannot just come and change it like that. It's basically read only! Only other members can modify it.
private: //Private so that it cannot be changed externally.
vector<T> DataHolder; //We create a vector that will hold all our parameters and data.
void Insert(){} //This is a member just for recursion. Used in recursive function. It's a base function.
Recursive Functions & Stack Overflows:
A recursive function is one that calls itself over and over. Thing is, if it does it too much, we get what is called a stack overflow. A stack overflow can be thought of like Jenga. You build from the bottom to the top. You keep calling your function, it will add to the stack every time.
Now with this, you want to clean the stack from the top to bottom. Why? Because you just can't pull from the bottom here. Now if the stack gets too high it will obviously topple over. Hence stack overflow because it literally overflows with data and can't hold anymore. Your program will then have memory leaks.
A regular function adds to the stack and gets removed after it's usage.
This recursive function breaks out of itself though and the stack is cleaned in order.
Templates & The Ellipsis Operator:
Now we finally create the templates and the function that will call itself to handle our infinite parameters.
The ellipsis operator aka … means packed. It packs a ton of arguments in that one operator. For us to use it, we have to unpack it and use each argument. For this we use a recursive function.
template<typename A, typename... Args> //Finally we create a variadic templated function. Note that it's called Insert.
ArrayOfAnything& Insert(A FirstArg, const Args... RemainingArgs) //This is Insert defined as a templated function of variable amounts of parameters.
{
this->DataHolder.push_back(FirstArg); //Of course we need to do something with the first argument.
Insert(RemainingArgs...); //Recursively call the Insert function which passes itself the rest of the parameters.
return *this; //Return our type.
}
In the above code, we created a template that takes argument A. This is our first argument. Next our template has a second argument which is the ellipsis which takes any amount of parameters. We call our insert function which constantly calls itself. Every time it is called, the next argument is added to our vector for use later. When there are no more arguments, the function returns a pointer to our type.
Overloading Operators ->Subscript:
Now we create some functions and overload some operators. When you overload an operator, you make it do what you want it to do. Basically if I overload the addition operator, I can make it behave like the minus sign. It will do whatever I command. I can make the + sign subtract and the - sign add. You are essentially creating your own operators for your own type.
Below I have one function that will give us the size of our vector. This is a public function so it can be called at anytime from anywhere. Next I overloaded the subscript operator so that if someone does:
ArrayOfAnything[3] it will return the value at index 3 in our DataHolder vector.
int size() {return DataHolder.size();} //This is to grab the size of our underlying vector.
ArrayOfAnything& operator [](int I) //We overload the subscript operator
{
assert(I >= 0 && !DataHolder.empty());
return DataHolder[I]; //return the value in our vector at index I.
}
const ArrayOfAnything& operator [](int I) const //Same thing as above except this is the const overload.
{
assert(I >= 0 && !DataHolder.empty());
return DataHolder[I]; //return the value in our vector at index I.
}
Overloading Operators ->Comparison:Next I overload two more operators which is the comparison operators. I overload them so that we can do if (ArrayOfAnything1 == ArrayOfAnything2)… This tells us if the two arrays are equal, itself, or different!
bool operator == (const ArrayOfAnything &AOA) const //So we want to check if two ArraysOfAnything are equal.. well we overload the comparisson operator.
{
return (DataHolder == AOA.DataHolder); //well return the comparisson of the two underlying vectors.
}
bool operator != (const ArrayOfAnything &AOA) const //DO the same as above except this is != operator. Not equal.
{
return !(DataHolder == AOA.DataHolder);
}
Overloading Operators ->Assignment:
The next operator I overload is the Assignment operator. This operator lets you assign one ArrayOfAnything To Another ArrayOfAnything. You can make both of them equal!
ArrayOfAnything& operator = (const ArrayOfAnything &AOA) //Ahh finally we want to assign data to our custom type. Thus we overload the assignment operator
{
if (this != &AOA) //First we check if we are assigning to ourselves. If not then continue.
{
if (DataHolder.size() > 0) //Now if our array already contains data then we have to clear it to allow it to equal the assigned array.
DataHolder.clear();
for (size_t I = 0; I < AOA.DataHolder.size(); I++) //Finally we loop through the assigned array and copy all it's data to ours.
DataHolder.push_back(AOA.DataHolder[I]);
}
return *this; //Both arrays are now equal so just return ours.
}
Overloading Operators ->Insertion:
Now the next bunch of code is a function we are all familiar with here if you have every use Simba. It is called the SetLength Function. C++ doesn't have this function naturally so of course we want to create our own. It's basically a one-line function. It will resize our underlying vector to whatever size we choose. We obviously don't NEED it at all but lets just have it anyway.
You will also see the overload of the Insertion operator. With this operator, we can insert a bunch of values at once. OR We can insert one at a time. Either way, all the values inserted will get added to our vector.
ArrayOfAnything& operator << (const T& t) //So we want to just insert one value at a time.. Thus we overload the insertion operator.
{
DataHolder.push_back(t);
return *this;
}
void SetLength(size_t NewLength) //Ahh the magical SetLength function. If for any reason we want to resize our vector/array, we use this.
{
DataHolder.resize(NewLength);
}
Overloading Operators ->Parameters:
Here is where we overload the brackets operator. AKA the Parameters operator. Why do we need to overload it? Well with this operator, we can keep adding values as much as we want without having to call the constructor again! Here we used our same old recursive Insert function to handle the infinite parameters for us. It adds every value inserted to our vector.
template<typename A, typename... Args>
ArrayOfAnything& operator ()(A FirstArg, const Args... RemainingArgs) //We are overloading the parameters operator. This allows us to recursively add infinite parameters
{ //Without calling the constructor again.
this->DataHolder.push_back(FirstArg);
Insert(RemainingArgs...);
return *this;
}
Overloading Operators ->Insertion OutStream:
Finally the last and longest/hardest operator for us to overload is the insertion operator outstream. Yes I know it sounds long. It has the same symbol as the insertion operator except that this operator will not insert into our vector/array/class. What it does is insert our class/vector/data into a stream that is used for output.
This allows us to PRINT our array exactly how we want it. Exactly like Simba does.
An outstream is just a stream that is used to display data on a user's screen or to use with another stream.
Now don't be scared by the large amount of code. We basically loop through our array, add it's data to a stream and return the stream for use later. What you do with the stream is your decision. You can print it, insert it into another stream, modify it, turn it into a string, etc.. Now I know your probably wondering what Friend is.
Friend allows outside functions to access members of our class even if they are private! The below function is NOT a member of our class because it is just a friend.
Thing is, friends can borrow from and use us as much as they want in C++ so this operator will have access to all our internals and what not. This is necessary so that this operator can access our DataHolder since it's private. Lets just say we gave this friend access to our entire life and hope it doesn't mess us up.
friend ostream& operator << (ostream& Str, const ArrayOfAnything &AOA) //Aha! Now this is where the real stuff come in. We overload the output operator!
{ //We want our array to print nicely and naturally without having to print each value one by one with a for-loop
if (!AOA.DataHolder.empty()) //First we check if we are empty.. if we are, there is nothing to print so forget it.
{
Str<<"["; //We want our array to print like: [a, b, c, d] So we put a bracket outside the loop.
for (size_t I = 0; I < AOA.DataHolder.size() - 1; I++) //We loop through our array until the second last element. Why? Because we don't want an extra comma.
{ //If we do until the last one, it will print like: [a, b, c, d,] <-- extra comma
Str<<AOA.DataHolder[I]; //Our array's Data will be added to our Output string..
Str<<", "; //Let us add a comma and a space after each value added to our string/output.
}
Str<<AOA.DataHolder[AOA.DataHolder.size() - 1]; //Finally we get the last element we had in our array but we don't add a comma.
Str<<"]"; //Outside our for-loop, we add the final bracket.
}
else
{
Str<<"[]"; //If our array was empty, we just print some nice brackets to let the user know it's blank.
}
return Str; //Finally we return the output/data/string we just created which holds all our data.
}
Usage:
The below example shows how to use the class. First I include my header because I have my class in a header. Of course you don't need it in a header. Next I start making some custom types! Arrays of Ints, 2D Arrays of Ints, 3D Arrays of ints. 3D Arrays of strings, doubles, classes, objects!
Yes you can even use this on classes and objects! An array of objects, classes, etc. ANYTHING! I demonstrated the use of infinite parameters. Note that I did NOT have to use setlength? This is because its fully dynamic!
Sample Code:
#include "ArrayOfAnything.h" //This header file holds our class for us =]
int main()
{
cout.flags(ios::boolalpha); //So it prints true and false instead of 1 & 0.
ArrayOfAnything<int> IA(1, 2, 3, 4, 5, 6, 7, 8, 9); //notice we can have any amount of parameters.. Don't stop at 9 :D
ArrayOfAnything<int> IA2(1, 5, 9, 7); //Like I said.. Any amount.
ArrayOfAnything<int> IA3; //Yeah I know it's empty.
ArrayOfAnything<int> IA4; //Also empty.
IA3<<1 << 5 << 9 << 7; //Just add any amount u want.
IA4(1, 2, 3, 4, 5, 6, 7, 8, 9); //Can also add values to it like that.
cout<< IA <<endl; //This should print [1, 2, 3, 4, 5, 6, 7, 8, 9];
cout<<(IA2 == IA3) <<endl; //This should print true because IA2 and IA3 have the same values.
cout<<(IA != IA4) <<endl; //This should print false because IA1 and IA4 are the exact same! They have the same values.
ArrayOfAnything<ArrayOfAnything<int> > I2DA; //This is a 2dimension array of integers.
I2DA<<IA; //We just inserted an array of ints into the 2D array.
I2DA(IA2, IA3); //Inserted two more.
I2DA<<IA4; //Added another one!
cout<<I2DA <<endl; //This should print [[1, 2, 3, 4, 5, 6, 7, 8, 9], [1, 5, 9, 7], [1, 5, 9, 7], [1, 2, 3, 4, 5, 6, 7, 8, 9]];
ArrayOfAnything<ArrayOfAnything<ArrayOfAnything<string> > > S3DA; //This is a 3D Array of Strings.
ArrayOfAnything<ArrayOfAnything<double> > D2DA; //This is a 2D array of Doubles.
//The dimensions can go on and on forever with an infinite amount of values if your comp has enough memory which it most certainly does.
}Finally this is the entire class in a Header:
#ifndef ARRAYOFANYTHING_H_INCLUDED
#define ARRAYOFANYTHING_H_INCLUDED
#include <iostream>
#include <vector>
using namespace std;
template<typename T>
class ArrayOfAnything
{
private:
vector<T> DataHolder;
void Insert(){}
template<typename A, typename... Args>
ArrayOfAnything& Insert(A FirstArg, const Args... RemainingArgs)
{
this->DataHolder.push_back(FirstArg);
Insert(RemainingArgs...);
return *this;
}
public:
ArrayOfAnything(){}
template<typename A, typename... Args>
ArrayOfAnything(A FirstArg, const Args... RemainingArgs)
{
this->DataHolder.push_back(FirstArg);
Insert(RemainingArgs...);
}
~ArrayOfAnything() {}
int size() {return DataHolder.size();}
ArrayOfAnything& operator [](int I)
{
assert(I >= 0 && !DataHolder.empty());
return DataHolder[I];
}
const ArrayOfAnything& operator [](int I) const
{
assert(I >= 0 && !DataHolder.empty());
return DataHolder[I];
}
bool operator == (const ArrayOfAnything &AOA) const
{
return (DataHolder == AOA.DataHolder);
}
bool operator != (const ArrayOfAnything &AOA) const
{
return !(DataHolder == AOA.DataHolder);
}
ArrayOfAnything& operator = (const ArrayOfAnything &AOA)
{
if (this != &AOA)
{
if (DataHolder.size() > 0)
DataHolder.clear();
for (size_t I = 0; I < AOA.DataHolder.size(); I++)
DataHolder.push_back(AOA.DataHolder[I]);
}
return *this;
}
ArrayOfAnything& operator << (const T& t)
{
DataHolder.push_back(t);
return *this;
}
void SetLength(size_t NewLength)
{
DataHolder.resize(NewLength);
}
template<typename A, typename... Args>
ArrayOfAnything& operator ()(A FirstArg, const Args... RemainingArgs)
{
this->DataHolder.push_back(FirstArg);
Insert(RemainingArgs...);
return *this;
}
friend ostream& operator << (ostream& Str, const ArrayOfAnything &AOA)
{
if (!AOA.DataHolder.empty())
{
Str<<"[";
for (size_t I = 0; I < AOA.DataHolder.size() - 1; I++)
{
Str<<AOA.DataHolder[I];
Str<<", ";
}
Str<<AOA.DataHolder[AOA.DataHolder.size() - 1];
Str<<"]";
}
else
{
Str<<"[]";
}
return Str;
}
};
#endif //ARRAYOFANYTHING_H_INCLUDED
Extras -> .Net Framework Version:
#ifndef TEMPLATES_H_INCLUDED
#define TEMPLATES_H_INCLUDED
using namespace System;
using namespace System::Collections::Generic;
template <typename T>
public ref class CustomType sealed
{
private:
List<T> TypeData;
public:
CustomType() {}
CustomType(...array<T^> ^Types) //Basically Variadic Templates.. As good as it gets for MS2010..
{
for each (T^ t in Types)
{
TypeData.Add(*t);
}
}
~CustomType() {}
int size(){ return (TypeData.Count); }
T^ operator [](int I)
{
if ((I < 0) || (this->size() == 0) || (I > this->size()))
throw gcnew System::ArgumentException("Parameter cannot be null");
return TypeData[I];
}
void operator()(...array<T^> ^Types)
{
for each (T^ t in Types)
{
TypeData.Add(*t);
}
}
operator const List<T>^ ()
{
return TypeData;
}
bool operator == (CustomType %CT)
{
for (int I = 0; I < CT.size(); I++)
{
if (this->TypeData[I] != CT.TypeData[I])
return false;
}
return true;
}
bool operator != (CustomType %CT)
{
for (int I = 0; I < CT.size(); I++)
{
if (this->TypeData[I] == CT.TypeData[I])
return false;
}
return true;
}
CustomType% operator = (CustomType %CT)
{
if (this != %CT)
{
if (this->size() > 0)
TypeData.Clear();
for (int I = 0; I < CT.size(); I++)
TypeData.Add(CT.TypeData[I]);
}
return *this;
}
CustomType% operator << (const T% t)
{
TypeData.Add(t);
return *this;
}
CustomType% operator << (CustomType %CT)
{
for (int I = 0; I < CT.size(); I++)
{
TypeData.Add(*CT[I]);
}
return *this;
}
CustomType% Clear()
{
TypeData.Clear();
return *this;
}
CustomType% Clear (const T% t)
{
TypeData.Remove(t);
return *this;
}
CustomType% Clear (CustomType %CT)
{
for (int I = 0; I < CT.size(); I++)
{
TypeData.Remove(*CT[I]);
}
return *this;
}
CustomType% ClearAt (int I)
{
TypeData.RemoveAt(I);
return *this;
}
CustomType% Insert (const T% Value, int Index)
{
TypeData.Insert(Index, Value);
return *this;
}
CustomType% Insert (CustomType %CT, int Index)
{
for (int I = 0; I < CT.size(); I++)
{
TypeData.Insert(Index++, *CT[I]);
}
return *this;
}
virtual String^ ToString() override
{
String^ Str;
if (this->size() != 0)
{
Str = "[";
for (int I = 0; I < this->size() - 1; I++)
{
Str += TypeData[I];
Str += ", ";
}
Str += TypeData[this->size() - 1];
Str += "]";
}
else
{
Str += "[]";
}
return Str;
}
};
#endif // TEMPLATES_H_INCLUDED
THE END.