Role of data structure in compiler design
Mithiun kumar gupta
Reg. no. – 11102557
Roll. No. – RD1107A13
1. ABSTRACT:-
Most programmers write software
in a high-level language, such as Pascal, C,
C++, java, Cobol and others. Many are not
aware of the fact that they are really
making use of a sophisticated program
called a compiler that bridges the gap
between their chosen language and a
computer architecture. Some have no
concept at all, or a very poor grasp of the
computer's instruction set, memory
organization, and other details that make
their software work. A compiler therefore
provides a valuable form of information
hiding. Most programmers don't need to
know anything about the details of
translation and execution, only the
properties claimed to be supported by the
high-level language and its libraries.
Compilers design are a valuable and
reliable tool for the most part, but no
software is perfect. If a bug arises in code,
it's sometimes useful to be able to trace it
down into the assembler/micro instruction
level. High performance may require
certain low-level operations. Finally,
knowing something about the strategy
used by a compiler helps the engineer
understand the high-level language at a
deeper level.
2. INTRODUCTION OF
COMPILER DESIGN
A compiler is a computer program
(or set of programs) that transforms source
code written in a programming language
(the source language) into another
computer language (the target language,
often having a binary form known as
object code). The most common reason for
wanting to transform source code is to
create an executable program.
The name "compiler" is primarily
used for programs that translate source
code from a high-level programming
language to a lower level language (e.g.,
assembly language or machine code). If
the compiled program can run on a
computer whose CPU or operating system
is different from the one on which the
compiler runs, the compiler is known as
across-compiler. A program that translates
from a low level language to a higher level
one is called a decompiles. A program that
translates between high-level languages is
usually called a language translator, source
to source translator, or language converter.
A language rewriter is usually a program
that translates the form of expressions
without a change of language.
A compiler is likely to perform
many or all of the following operations:
lexical analysis, pre-processing, parsing,
semantic analysis (Syntax-directed
translation), code generating, and code
optimization.
Program faults caused by incorrect
compiler behaviour can be very difficult to
track down and work around; therefore,
compiler implementers invest a lot of time
ensuring the correctness of their software.
Compiler design is focusing on
more low-level and systems aspects rather
than high-level questions such as
polymorphic type inference or separate
compilation. You will be building several
complete end-to-end compilers for
successively more complex languages,
culminating in a mildly optimizing
compiler for a safe variant of the C
programming language to x86-64
assembly language. For the last project
you will have the opportunity to optimize
more aggressively, to implement a garbage
collector, or retarget the compiler to an
abstract machine.
3. REQUIREMENT OF
COMPILER DESIGN
There are many requirement related
to compiler as follow:-
CORRECTNESS:-
Correctness is absolutely
paramount. A buggy compiler is next to
useless in practice. Since we cannot
formally prove the correctness of your
compilers, we use extensive testing. This
testing is end-to-end, verifying the
correctness of the generated code on
sample inputs. We also verify that your
compiler rejects programs as expected
when the input is not well-formed
(lexically, syntactically, or with respect to
the static semantics), and that the
generated code raises an exception as
expected if the language specification
prescribes this. We go so far as to test that
your generated code fails to terminate
(with a time-out) when the source program
should diverge. Emphasis on correctness
means that we very carefully define the
semantics of the source language. The
semantics of the target language is given
by the GNU assembler on the lab
machines together with the semantics of
the actually machine. Unlike C, we try to
make sure that as little as possible about
the source language remains undefined.
EFFICIENCY: -
In a production compiler,
efficiency of the generated code and also
efficiency of the compiler itself are
important considerations. In this course,
we set very lax targets for both,
emphasizing correctness instead. In one of
the later labs in the course, you will have
the opportunity to optimize the generated
code. The early emphasis on correctness
has consequences for your approach to the
design of the implementation. Modularity
and simplicity of the code are important
for two reasons: first, your code is much
more likely to be correct, and, second, you
will be able to respond to changes in the
source language specification from lab to
lab much more easily.
INTEROPERABI LITY: -
Programs do not run in isolation,
but are linked with library code before
they are executed, or will be called as a
library from other code. This puts some
additional requirements on the compiler,
which must respect certain interface
specifications. This means that you will
have to respect calling conventions early
on (for example, properly save caller-save
registers) and data layout conventions
later, when code will be calling library
functions.
Usability:-
A compiler interacts with the
programmer primarily when there are
errors in the program. As such, it should
give helpful error messages. Also,
compilers may be instructed to generate
debug information together with
executable code in order help users debug
runtime errors in their program.
In this course, we will not formally
evaluate the quality or detail of your error
messages, although you should strive to
achieve at least a minimum standard so
that you can use your own compiler
effectively.
Retarget ability:-
At the outset, we think of a
compiler of going from one source
language to one target language. In
practice, compilers may be required to
generate more than one target from a given
source (for example, x86-64 and ARM
code), sometimes at very different levels
of abstraction (for example, x86-64
assembly or LLVM intermediate code).
THE STRUCTURE OF COMPILER
DESIGN
Compilers bridge source programs
in high-level languages with the
underlying hardware. A compiler
requires;-
1) determining the correctness of the
syntax of programs,
2) generating correct and efficient object
code,
3) run-time organization, and
4) formatting output according to
assembler and/or linker conventions.
A compiler consists of three main
parts: the frontend, the middle-end, and the
backend.
The front end: -
It checks whether the program is
correctly written in terms of the
programming language syntax and
semantics. Here legal and illegal programs
are recognized. Errors are reported, if any,
in a useful way. Type checking is also
performed by collecting type information.
The frontend then generates an
intermediate representation or IR of the
source code for processing by the middle-
end.
The middle end: -
It is indicate where optimization
takes place. Typical transformations for
optimization are removal of useless or
unreachable code, discovery and
propagation of constant values, relocation
of computation to a less frequently
executed place (e.g., out of a loop), or
specialization of computation based on the
context. The middle-end generates another
IR for the following backend. Most
optimization efforts are focused on this
part.
The back end:- is responsible for
translating the IR from the middle-end into
assembly code. The target instruction(s)
are chosen for each IR instruction.
Register allocation assigns processor
registers for the program variables where
possible. The backend utilizes the
hardware by figuring out how to keep
parallel execution units busy, filling delay
slots, and so on. Although most algorithms
for optimization are in NP, heuristic
techniques are well-developed.
Figure of structure of compiler design
We have seen different applications
of template meta-programming such as
static data structures, algorithms, design
patterns, Reflection, expression templates,
and number theory. Compile Time Data
Structure is, in fact, not a new concept in
C++. Further information about it can be
found in the References. Here, we are
going to study the Linked List as an
example of a compile time data structure,
and will try to implement it with template
meta-programming.
Template meta-programming is
usually difficult to understand at first,
especially for those who are not familiar
with it. Therefore, we will discuss the run-
time counterpart at the same time. We use
a naming convention “List” for all the
runtime programs to distinguish it with the
compile-time version. Although we can
implement the run-time programs in more
than one way, in the compile-time world,
we have only recursion to do everything
including looping; therefore, we are going
to use recursion in the run-time version to
compile Time Linked List
If we are going to make a single
linked list, then our structureof linked list
would be something like this:
/////////////////////////////////////////////////////////// //
Node of Runtime Link List
///////////////////////////////////////////////////////////
struct ListNode
{
int value;ListNode* next;
};
Here is the compile time version of this
structure:
/////////////////////////////////////////////////////////// //
Termination of Link List
///////////////////////////////////////////////////////////
struct End
{
};
/////////////////////////////////////////////////////////// //
Node of Static Link List
///////////////////////////////////////////////////////////
template <int iData, typename Type>
struct Node
{
enum { value = iData };
typedef Type Next;};
Here, we need one more structure
to indicate the termination condition of the
linked list. You can also call it is an end
marker. In the runtime version, we don’t
need it because in that case, we simply
check the value of the next filed in the
node. If its value is NULL, then it means
that it is the last node of the linked list
However, in the case of template
meta-programming, we have to do
template specialization (or partial template
specialization) to stop the recursion. We
can do template specialization for any
specific type or for any specific value.
Here, we can’t do template specialization
on a value, because the second template
parameter is a type. Therefore, we have to
create a new type to stop the recursive
instantiation of the template. The name of
the end marker can be anything, and it can
store whatever you like it to. In our
example, it would be sufficient to make an
empty structure to create a new type as an
end marker.
Now, let’s try to implement a few
auxiliary functions that work on lists. Here
is our first function to insert data in the
linked list. We explicitly made its name
look similar to the member function of the
STL list class, because we are going to
implement a few more STL algorithms.
Here is the simplest
implementation for inserting items in the
runtime single linked list.
///////////////////////////////////////////////////////////
// Insert item into Runtime Link List
///////////////////////////////////////////////////////////
void ListPushBack(ListNode** pHead, int value)
{
if(*pHead==NULL)
{
*pHead = new ListNode();
(*pHead)->value = value;
}
Else
{
ListPushBack(&(*pHead)->next, value);
}
}
The name of the linked list is
prefixed with “List” to distinguish it from
the compile time version.
Interestingly, the compile time
version of the same function doesn’t use
recursion, and its implementation is very
easy
///////////////////////////////////////////////////////////
// Insert item into Static Link List
///////////////////////////////////////////////////////////
template <int iData, typename Type>
struct PushBack
{
typedef Node<iData, Type> staticList;
};
And, here is the usage of this function at
compile time:
typedef PushBack<2, End>::staticList node1;
typedef PushBack<4, node1>::staticList node2;
typedef PushBack<6, node2>::staticList node3;
typedef PushBack<8, node3>::staticList node4;
typedef PushBack<10, node4>::staticList node5;
typedef PushBack<12, node5>::staticList myList;
Although we can create a static linked list
like this:
typedef Node<-15, Node<15, Node<18, Node<13, End>
> > >myList;
the above method to create a static
linked list has a few advantages, which we
will see when we implement a few STL
algorithms in the compile-time version.
Now, let’s implement a few more
STL list algorithms at compile time. But,
first take a look at its runtime version to
better understand template meta-
programming.
///////////////////////////////////////////////////////////
// Structure to calculate the length of
Runtime Link List
///////////////////////////////////////////////////////////
int ListSize(ListNode* pHead)
{
if (pHead == NULL)
return 0;
else
return 1 + ListSize(pHead->next);
}
This function is quite simple, and
uses tail recursion for optimization. The
compiler can optimize tail recursion with
looping to avoid any runtime stack
overhead. Here is the compile-time version
of the same function:
///////////////////////////////////////////////////////////
// Structure to calculate the length of Static
Link List
///////////////////////////////////////////////////////////
template <typename T>
struct Size;
template <int iData, typename Type>
struct Size<Node<iData, Type> >
{
enum { value = 1 + Size<Type>::value };
};
template <>
struct Size<End>
{enum { value = 0 };
};
Although the STL list class doesn’t
have an at() function, because list doesn’t
have a random access iterator, we are
trying to implement this function for the
linked list. Because we can’t access any
item of the linked list randomly, it is a
linear time function, not a constant time
one just like the at() function of the vector.
Here is the simple run-time
implementation of the at() function on a
single linked list with linear complexity:
///////////////////////////////////////////////////////////
// Structure to find item from specific
location from RuntimeLink List
///////////////////////////////////////////////////////////
int ListAt(ListNode* pHead, int iPos)
{
static int iIndex = 0;
++iIndex;
if (iIndex == iPos)
return pHead->value;
else if (pHead->next == NULL)
return -1;
else
return ListAt(pHead->next, iPos);
}
The code presented here is just a
proof of concept, not a production quality
code. One major problem with this
function is the return code. If the input
position is greater than the length of the
linked list, like the length of the linked list
is 4, but we are trying to access 6th
element, then this function returns-1. This
code is misleading, because -1 can be a
data in the linked list at a given position,
so it would be impossible to differentiate
whether it is an error code or the actual
data at the given position.
This one is a much better
implementation than the previous one:
///////////////////////////////////////////////////////////
// Structure to find item from specific
location from Runtime Link List
///////////////////////////////////////////////////////////
bool ListAt(ListNode* pHead, int iPos, int* iVal)
{
static int iIndex = 0;
++iIndex;
if (iIndex == iPos)
{
*iVal = pHead->value;return true;
}
else if (pHead->next == NULL)
{return false; }
Else
{
return ListAt(pHead->next, iPos, iVal);
}
}
This function returns the value at a
specific location by parameter. If the user
passes a position that is greater than the
length of the linked list, then it will return
false; otherwise, it stores the value at the
parameter and returns true.
Here is the usage of this function:
if (pHead != NULL)
{
int val;
if (ListAt(pHead, 3, ∓val))
{std::cout << val << std::endl;}
}
Here is the compile time version of the
same function:
///////////////////////////////////////////////////////////
// Structure to find item from specific
location from Static Link List
///////////////////////////////////////////////////////////
template <int iIndex, typename T, int iStart = 0>struct
At;
template <int iIndex, int iData, typename Type, int
iStart>struct At<iIndex, Node<iData, Type>, iStart>
{
enum { value = iIndex == iStart ?iData : At<iIndex,
Type, (iStart + 1)>::value };
};
template <int iIndex, int iData, int iStart>struct
At<iIndex, Node<iData, End>, iStart>
{
enum { value = iIndex == iStart ? iData : -1 };
};
This program has the same
problem that it returns -1 when the item is
not found. In template meta-programming,
we can’t return a value by parameter just
like its run-time equivalent. The solution is
to introduce one more enum variable
inside the structure to store whether the
item was found or not.
Here is the next version of the
same program:
///////////////////////////////////////////////////////////
// Structure to find item from specific
location from Static Link List
///////////////////////////////////////////////////////////
template <int iIndex, typename T, int iStart = 0>struct
At;
template <int iIndex, int iData, typename Type, int
iStart>struct At<iIndex, Node<iData, Type>, iStart>
{
enum { value = iIndex == iStart ?iData : At<iIndex,
Type, (iStart + 1)>::value };
enum { found = iIndex == iStart ? 1 :At<iIndex, Type,
(iStart + 1)>::found };
};
template <int iIndex, int iData, int iStart>struct
At<iIndex, Node<iData, End>, iStart>
{
enum { value = iIndex == iStart ? iData : -1 };
enum { found = iIndex == iStart ? 1 : 0 };
};
Although the value variable still
stores -1 when an item not found in the
linked list, if we use the other variable, i.e.,
the “found” variable, then we can ignore
this value.
Here is the usage of this algorithm :
if (At<8, myList>::found == 1)
{
std::cout << At<8, myList>::value <<
std::endl;
}
4. Compile Time Algorithms
In this section, we are going to
study different STL algorithms and their
working with the STL list class, and how
to implement those at compile time using
template meta-programming. From now,
instead of using creating our own single
linked list and implementing all the related
functions ourselves, we are going to use
the STL list class.
4.1. Find Algorithm
The Find algorithm returns the first
occurrence of the specified value in the
given range. If it couldn’t find the
specified value, then it returns the end
iterator, i.e., one past the last element
given a range.
Here is a simple usage of the Find
algorithm onan STL list:
std::list<int>lst;
lst.push_back(7);
lst.push_back(14);
lst.push_back(21);
lst.push_back(28);
lst.push_back(35);
std::list<int>::iterator iter_ = std::find(lst.begin(),
lst.end(), 7);
if (iter_ != lst.end())
std::cout << *iter_ << std::endl;
else
std::cout << "Not found" << std::endl;
Here is the closest implementation
of the Find algorithm in template meta-
programming:
///////////////////////////////////////////////////////////
// Structure to find the location of specific
item in Static Link List
///////////////////////////////////////////////////////////
template <typename TBegin,typename TEnd,int
iFindData,int iStart = 0>struct Find;
template <int iData,typename TBegin,typename TEnd,int
iFindData,int iStart>
struct Find<Node<iData, TBegin>, TEnd, iFindData,
iStart>
{
enum { value = iFindData == iData ?
iStart :Find<TBegin, TEnd, iFindData, (iStart +
1)>::value };
};
template <int iData,typename TEnd,int iFindData,int
iStart>
struct Find<Node<iData, TEnd>, TEnd, iFindData,
iStart>
{
enum { value = iFindData == iData ? iStart : -1 };
};
template <typename TEnd, int iFindData>struct
Find<TEnd, TEnd, iFindData>
{
enum { value = -1 };
};
This implementation returns the
position of the specified value, if found in
the given range of a single linked list;
otherwise, it returns -1.
Here is the usage of the above
implementation:
typedef PushBack<7, End>::staticList node1;
typedef PushBack<14, node1>::staticList node2;
typedef PushBack<21, node2>::staticList node3;
typedef PushBack<28, node3>::staticList node4;
typedef PushBack<35, node4>::staticList
myList;std::cout << Find<myList, End, 7>::value <<
std::endl;
We can even pass the range, just
like in the STL algorithms, to find
elements in between a given range.
std::cout << Find<node3, node1, 7>::value << std::endl;
This is one of the reasons to use the
static version of the Push Back
implementation rather than create a whole
static linked list in one statement. By using
the compile time version of the Push Back
implementation, we are able to get the
iterator equivalent in the compile time
world.
4.2. Count Algorithm
This algorithm returns the numbers
of items in a given range that has the given
value.
Here is a sample to demonstrate
this:
std::list<int>
lst;lst.push_back(7);
lst.push_back(14);
lst.push_back(7);
lst.push_back(14);
lst.push_back(21);
lst.push_back(7);
std::cout << std::count(lst.begin(), lst.end(), 7)
<< std::endl;
The output of this program is 3,
because 7 exists three times inthe given
range. Here is a similar algorithm
implemented atcompile time:
///////////////////////////////////////////////////////////
// Count Algorithm. Returns the number
of elements in StaticLink List
///////////////////////////////////////////////////////////
template <typename TBegin,
typename TEnd,
int iVal>struct Count;
template <int iData,
typename TBegin,
typename TEnd,
int iVal>struct Count<Node<iData, TBegin>, TEnd,
iVal>
{
enum { value = (iData == iVal ? 1 : 0) +
Count<TBegin, TEnd, iVal>::value };
};
template <int iData, typename TEnd, int iVal>struct
Count<Node<iData, TEnd>, TEnd, iVal>
{
enum { value = iData == iVal ? 1 : 0 };
};
template <typename TEnd, int iVal>struct Count<TEnd,
TEnd, iVal>
{
enum { value = 0 };
};
This implementation is very similar
to the “Find” algorithm, and its usage is
also similar to that. Here is the closest
implementation of the compile-time
version of the same code we saw in this
section using the STL “count” algorithm.
typedef PushBack<7, End>::staticList node1;
typedef PushBack<14, node1>::staticList
node2;
typedef PushBack<7, node2>::staticList node3;
typedef PushBack<14, node3>::staticList
node4;
typedef PushBack<21, node4>::staticList
node5;
typedef PushBack<7, node5>::staticList
myList;
std::cout << Count<myList, End, 7>::value <<
std::endl;
Just like the “Find” algorithm, we
can also count the number of specified
items in a given range rather than
searching in the complete static linked list.
4.3. Find If Algorithm
If we want to find whether a linked
list contains an item that is greater than 10
but less than 20, then we couldn’t do it
with the Find algorithm. Here is the
solution to this problem, the Find If
algorithm. This algorithm returns the
iterator of the element in the linked list that
satisfies the condition in the given
predicate. The predicate can be a simple
function or a function object, i.e., a class
with an overloaded () operator, that returns
true. In that function (or function object),
we can check any condition we want. Let’s
take a look at the run-time version to better
understand it before going to the compile
time version.
std::list<int>lst;
lst.push_back(7);
lst.push_back(14);
lst.push_back(7);
lst.push_back(14);
lst.push_back(21);
lst.push_back(7);
std::list<int>::iterator iter_
=std::find_if(lst.begin(), lst.end(), MyPredicate());
if (iter_ != lst.end())
std::cout << *iter_ << std::endl;
else
std::cout << "Not found" << std::endl;
Here, we pass MyPridicate as a
function object, and here is the
implementation of it:
struct MyPredicate
{
bool operator()(int val)
{
return val > 10 && val < 20 ? true : false;
}
};
Implementing the compile time
version of The Find If algorithm is not
very difficult, and its looks very similar to
the Find algorithm, except one addition to
the predicate type parameter. Here is the
compile time version of theFind If
algorithm:
///////////////////////////////////////////////////////////
// Find If Algorithm.
// Locate the specific items in Static Link
List that satisfy thepredicate
///////////////////////////////////////////////////////////
template <typename TBegin,
typename TEnd,
template <int iData> class Predicate,
int iStart = 0>
struct FindIf;
template <int iData,typename TBegin,
typename TEnd,
template <int iData> class Predicate,
int iStart>
struct FindIf<Node<iData, TBegin>,
TEnd, Predicate, iStart>
{
enum { value = Predicate<iData>::value
== 1 ? iStart :FindIf<TBegin, TEnd,
Predicate, (iStart+1)>::value };
};
template <int iData,typename TEnd,
template <int iData>
class Predicate,int iStart>struct
FindIf<Node<iData, TEnd>, TEnd,
Predicate, iStart>
{
enum { value = Predicate<iData>::value
== 1 ? iStart : -1 };
};
template <typename TEnd,
template <int iData> class Predicate>struct
FindIf<TEnd, TEnd, Predicate>
{enum { value = -1 };
};
Here we use template-template
parameter to make it easy tocall and
similar to STL algorithm. Here is the usage
of this algorithm.
typedef PushBack<7, End>::staticList node1;
typedef PushBack<14, node1>::staticList node2;
typedef PushBack<7, node2>::staticList node3;
typedef PushBack<14, node3>::staticList node4;
typedef PushBack<21, node4>::staticList node5;
typedef PushBack<7, node5>::staticList myList;std::cout
<< FindIf<myList, End, MyPredicate>::value
<<std::endl;
In compile time world, the
predicate is a structure (or class), not a
function or function object. Here is our
compile time version of the same predicate
we used earlier in this section for the run
time version:
template <int val>struct MyPredicate
{
enum { value = val > 10 && val < 20 ? 1 : 0 };
};
4.4. Count If Algorithm
The “Count if” algorithm is a
cousin of the “Find If”; the only difference
is, it will return the number of elements in
the linked list that satisfies the condition
given in the predicate. Let’s take a look at
the run time version of this algorithm first.
std::list<int>lst;
lst.push_back(7);
lst.push_back(14);
lst.push_back(7);
lst.push_back(14);
lst.push_back(21);
lst.push_back(7);
std::cout << std::count_if(lst.begin(), lst.end(),
MyPredicate())<< std::endl;
And, we use the same predicate that we
made earlier:
struct MyPredicate{bool operator()(int val)
{
return val > 10 && val < 20 ? true : false;
}
};
The output of this program is 2,
because there are two elements in the list
which are greater than 10 and less than 20.
Now, let’s take a look at the compile time
version of the same algorithm.
///////////////////////////////////////////////////////////
// Count If Algorithm.
// Returns the number of elements in Static
Link List
// that satisfy the predicate condition
///////////////////////////////////////////////////////////
template <typename TBegin, typename TEnd,template
<int iData> class Predicate>struct CountIf;
template <int iData, typename TBegin, typename
TEnd,template <int iData> class Predicate>struct
CountIf<Node<iData, TBegin>, TEnd, Predicate>
{
enum { value = (Predicate<iData>::value)
+CountIf<TBegin, TEnd, Predicate>::value };
};
template <int iData, typename TEnd,template <int
iData> class Predicate>struct CountIf<Node<iData,
TEnd>, TEnd, Predicate>
{
enum { value = Predicate<iData>::value };
};
template <typename TEnd,template <int iData> class
Prediate>struct CountIf<TEnd, TEnd, Prediate>
{enum { value = 0 };
};
This implementation is very similar
to the “Find If” algorithm. The only
difference is, here, we are actually count
the number of elements that satisfy the
condition given in the predicate rather than
find it. Here is the usage of this algorithm:
typedef PushBack<7, End>::staticList node1;
typedef PushBack<14, node1>::staticList
node2;
typedef PushBack<7, node2>::staticList node3;
typedef PushBack<14, node3>::staticList
node4;
typedef PushBack<21, node4>::staticList
node5;
typedef PushBack<7, node5>::staticList
myList;std::cout << CountIf<myList, End,
MyPredicate>::value <<std::endl;
4.5. Min, Max Algorithm
Although the STL version of the
min and max algorithms don’t work on a
range, but for consistency, we
implemented both algorithms in a similar
way, i.e., you can find the maximum or
minimum value in a static linked list in a
given range.
Here are the simple implementations of
both the algorithms:
///////////////////////////////////////////////////////////
// Structure to find the Maximum value in
the Link List
///////////////////////////////////////////////////////////
template <typename TBegin, typename TEnd>struct
Max;
template <int iData, typename TBegin, typename
TEnd>struct Max<Node<iData, TBegin>, TEnd >
{
enum { value = iData > Max<TBegin, TEnd>::value ?
iData : Max<TBegin, TEnd>::value };
};
template <int iData, typename TEnd>struct
Max<Node<iData, TEnd>, TEnd>
{
enum { value = iData };
};
///////////////////////////////////////////////////////////
// Structure to find the Minimum value in
the Link List
///////////////////////////////////////////////////////////
template <typename TBegin, typename TEnd>struct
Min;
template <int iData, typename TBegin, typename
TEnd>struct Min<Node<iData, TBegin>, TEnd>
{
enum { value = iData < Min<TBegin, TEnd>::value ?
iData : Min<TBegin, TEnd>::value };
};
template <int iData, typename TEnd>struct
Min<Node<iData, TEnd>, TEnd>
{
enum { value = iData };
};
And, here is a simple usage of
these algorithms:
std::cout << Max<myList, End>::value <<
std::endl;
std::cout << Min<myList, End>::value <<
std::endl;
If we use the same static linked list
that we created in the previous section,
then we will get 21 and 7 as the output as
the maximum and minimum values in the
static linked list, respectively. Just like the
other algorithms, here, we can also specify
the range to get the maximum or minimum
value in a given range, not in the complete
static linked list.
4.6. For Each Algorithm
Till now, all the algorithms we
discussed were just getting information
from the linked list. None of the above
algorithms changed any values in the
linked list. This is because once you have
some value, then it is very difficult, if not
impossible, to change the value we
assigned at compile time. Now, we are
going to discuss an algorithm that
performs some operation on the values of
the linked list, but here, we explicitly
restrict ourselves to not change any value
in the linked list. Let’s take a look at this
simple example of the “For Each”
algorithm to print the elements of the
linked list:
std::for_each(lst.begin(),
lst.end(),PrintFunctionObject());
Here, “PrintFunctionObject” is our function
object to do the actual work. Here is a simple
implementation of this predicate:
struct PrintFunctionObject
{
void operator()(int val)
{std::cout << val << std::endl;}
};
Remember, it can also be a simple function
instead of a function object. Here is the
compile time equivalent version of the
same algorithm:
///////////////////////////////////////////////////////////
// For each algorithm. Apply some action
on all items of the list
///////////////////////////////////////////////////////////
template <typename TBegin, typename TEnd,template
<typename T> class Function>struct ForEach;
template <int iData, typename TBegin, typename
TEnd,template <typename T> class Function>struct
ForEach<Node<iData, TBegin>, TEnd, Function>
{
void operator ()()
{
Function<TBegin>()(iData);
ForEach<TBegin, TEnd, Function>()();
}
};
template <int iData, typename TEnd,template <typename
T> class Function>struct ForEach<Node<iData, TEnd>,
TEnd, Function>
{
void operator ()(){Function<TEnd>()(iData);
}
} ;
template <typename TEnd,template <typename T> class
Function>struct ForEach<TEnd, TEnd, Function>
{void operator ()(){}
};
If you have taken a close look at
this implementation, then you may have
already noticed that this implementation is
quite different from all of those we
discussed earlier. In this implementation,
there isn’t any “enum” variable; on the
contrary, here we have the overload ()
operator. This is because when we have to
perform some action, we have to call some
function to do the actual work. This
program is actually a mixture of run-time
and compile-time programming. All of the
programs we discussed earlier were
completely resolved by the compiler at the
time of compiling and the actual result
stored in the executable. However, in this
case, although we are doing compile time
computation during compilation using
Recursion, the actual function call will still
execute when you run this program. This is
the only way to perform some IO
operations while doing template meta-
programming, i.e., using functions that
will execute at runtime. Here, we are not
only limited to performing IO operations
but can do a few more interesting things,
like create a class using the new operator
or a set of class hierarchies, as described in
“Modern C++ Design”[1]. Here is the
function object for this compile time
implementation of the “For Each”
algorithm.
template <typename T>struct
PrintFunctonObject
{
void operator ()(int iData)
{std::cout << iData << std::endl;}
};
The usage of this algorithm is also
slightly different. Here, we actually want
to execute some code at run-time;
therefore, we have to create an object of
the “ForEach” structure. Here is one
possible way to execute this algorithm
using an anonymous object:
ACKNOWLEGEMENT
Firstly I want to say to thanks of
my subject teacher and education
management, because they think able me
of this topic of term paper and give the
golden opportunity to prepare of term
paper. M y t e r m p a p e r t o p i c i s
“ R o l e o f D a t a Structure in Compiler
Design”. This topic is related to my
subject Data Structure; this topic help
know how can compile the software’s.
This topic is clear my more concept. I wish
to express to my gratitude to all the people
involved in the writing of this project,
specially “Sandeep Sharma” sir who was
very generous in sharing their time and
knowledge with me. I thank to “LPU” Wi-
Fi; they are give fully support.
REFERENCES:
(1) http://www.engr.sjsu.edu/
wbarrett/Parser/CompDesign.pdf
(2) http://www.capsl.udel.edu/
conferences/open64/2008/Papers/
111.pdf
(3) http://en.wikipedia.org/wiki/
Compiler
(4) Modern C++ Design Generic Programming.
(5) C++ Template Metaprogramming: Concepts.
(6) Reflection support by means of template Meta- programming , Guiseppe Attardi, Antonio Cisternino
(7) Static Data Structure: Reconciling Template Meta- programming and Generic Programming.
(8) Loops, Metaloops & C++ , Roshan Naik.6. Expression Templates, Veldhuizen, C++ Report 1995,Generative Programming – Methods, Tools and Applications7. Template Meta Programm