Hello again! In this video, we are going to start our section on compile time programming.
Compile time programming means we are writing a code which will be executed by the compiler, during the
compilation, and not by the program at run time.
So none of this code will actually appear in the program. Just the result of the computation.
So this means that, when the program runs, it instantly has access to the result, and does not need
to perform any calculation itself.
So that means the operation takes zero time. And you cannot really get any faster than that!
So obviously being able to do this is very desirable, but there are practical limitations, on what you
can do, and how easily you can do it.
This was actually possible in C, using something called "preprocessor macros".
So the processor is the very first stage of compilation.
This is when the compiler goes through and removes comments, compresses whitespace and so on.
You can give directives to the preprocessor, which start with a hash symbol.
And one of the directives you can give is to define a symbol. So you can put hash define some symbol.
And the prerocessor will replace that symbol, by whatever you define it as.
And you can provide arguments, and then the arguments will be substituted.
So here we have our macro function.
It just takes two arguments, and returns whichever one is the greater.
If we put something like max(a, b) in our code, this will substitute x with a and y with b, and then
paste the result into the compiler's view of the source code.
So the next stage of compilation will actually see this, instead of the max().
There are a few problems with this.
The first one is, there is no type information at all, because the compiler has not actually got that far
yet.
This is just text processing. So there is no type safety here at all.
The second one is, we need to put some brackets around the arguments when we use them, because of the
operator precedence rules.
We may get things which behave unexpectedly, or just do not compile.
And the third one is, if any of these arguments have side effects, then they may be evaluated more
than once.
For example, if we increment the argument, then this will be replaced by code which looks like this.
So if incrementing "a" is bigger than "b", then "a" gets incremented again.
So in that case, "a" is incremented twice.
On the other hand, if incrementing "a" is less than "b", then it only gets incremented once.
So you have inconsistent behavior.
And also, it is not obvious from looking at this code,
that "a" might be incremented twice.
So if you do fairly standard things, there is no problem.
But when we do the increment, it gets incremented twice, in the case where ++a is greater than
"b".
C++ has had templates for quite a long time.
These were originally introduced to support generic programming.
So, you all know about things like the vector class in the standard library.
The actual code for handling vectors is all the same.
The only thing which differs is the type of the element.
So you can write code which works with a generic element type, and then provide the type. And the compiler
will generate the correct code for that type.
Later on, some clever person discovered that templates actually implement a Turing-complete programming language.
So that means that, in theory, you can solve any problem using templates. If you are masochistic enough!
You can use the template parameters for representing state variables.
You can use recursive instantiation to simulate loops.
(Sorry, I have a bit of a headache coming on)
(Yeah,
okay.)
You can use template specialization for implementing controls.
(Oh)
(It is no good)
(I am sorry)
And you can also use integer operations to calculate results, which is actually not very painful.
(But I am sorry)
(I have had enough already. )
Template
metaprogramming is mainly done by library developers.
So if you mainly write applications, you do not need to worry too much about it.
It can be used for compile-time programming.
It can even be used for the original intended purpose, which is writing generic code, to avoid duplication.
The Standard Template Library is a good example.
You can use template metaprogramming for making decisions at compile time.
For example, if your hardware has some special features, you can take advantage of those, to make your
codes more efficient.
Or you can write code to make it easier to port to different computers.
You can use it for domain-specific programming so you can write a kind of language on top of C++,
which is customized for the problem that you are trying to solve.
And you can use it for expressing complex software patterns and concepts.
Templates metaprogramming mostly uses class templates, rather than functions.
There is a convention: if you have a member which represents a numeric result, or some kind of value, that is
called "value".
And if the member represents a type, then it is called "type".
Template
metaprogramming makes heavy use of template specialization. C++11 introduced something called "type
traits".
You can use these to get information about the properties of types.
For example, if you want to know if your templates parameter is an arithmetic type, you can do something
like that.
Let's have a quick demo!
So, is_arithmetic.
This is actually a template
claas. The class has a member called value, which is a bool.
And this will be true if int is an arithmetic type, and it will be false if not.
So we expect this to be true.
is_floating_point, with parameter int. We expect that to be false.
is_class. with parameter A?
Here is our class A.
So we expect that to be true.
And then, is_pointer, with a C type string as parameter.
So we expect that to be true as well.
(Do not know why this is says false. Sorry about that)
Okay.
So those are the results that we would expect.
So maybe nothing too surprising.
There are lots and lots of these. And most of them are pretty obscure, actually.
So that just gives you an idea of what is possible.
So with compile-time programming, until C++ 11, your only real option was templates.
The syntax for these is clumsy and verbose.
Doing logical operations is very complicated and hard to understand. The error messages are extremely
hard to understand as well. And you cannot just go into the debugger and see what is going on.
A debugger requires a running program in memory, and these are actually inside the compiler.
With C++11, we have constexpr. So this allows you to write things that look like normal C++ code,
but are executed by the compiler, at compile time, rather than runtime.
And the error messages look like the errors you get with normal C++ code, and not the horrible ones you
get with templates.
Okay.
So that is it for this video.
I will see you next time.
But until then, keep coding!