I have been working on a project called: Functional Programming Features of C++11/14 (for one of my subjects at university). There are several existing sources and similar presentations about such topics and I found one not so long ago which contained several code snippets that I haven't managed to understand completely (and somehow they can be connected to functional programming). Snippets A and B belonged to recursion and C belonged to lazy evaluation. I'd like to share them with you below:
Snippet A:
#include <iostream>
template <int N>
struct Factorial {
static int const val = N * Factorial<N - 1>::val;
};
template <>
struct Factorial <0> {
static int const val = 1;
};
int main() {
int factorial_of_6 = Factorial<6>::val;
std::cout << factorial_of_6 << std::endl;
return 0;
}
Was the point here compile time evaluation (in order to avoid runtime computations and improve performance)? Or are there other advantages, too?
Snippet B:
#include <iostream>
template <int ...>
struct my_sum;
template <>
struct my_sum <> {
static const int value {0};
};
template <int i, int ... tail>
struct my_sum <i, tail ...> {
static const int value = i + my_sum<tail ...>::value;
};
int main() {
int sum {my_sum<1, 2, 3, 4, 5>::value};
std::cout << sum << std::endl;
return 0;
}
Same question applies as above.
And here is another snippet which is maybe similar:
Snippet C:
#include <iostream>
template <typename... Args>
void some_function (Args ...) {
std::cout << sizeof...(Args) << std::endl;
}
int main() {
some_function ("Every little thing gonna be alright...", 1.0 / 0.0);
return 0;
}
"The presentation said: C++ is eager but the following will work." Is its point that until I don't care about the given expressions I can tell the quantity of them?
Please, be as specific and detailed as possible, and thank you very much for your patience and help in advance. :)
Snippet A
This is called Template Metaprogramming
, which is basically a technique using templates to generate code at compile time. This increases run time performance, because the calculations aren't done at run time, but rather at compile time.
Snippet A calculates the factorial of a given number at compile time:
template <int N>
struct Factorial {
static int const val = N * Factorial<N - 1>::val;
};
This defines a struct
Factorial
as a template which takes an int
. In that struct
, there is a static const
variable. The variable is static
, so that you don't have to create an instance of Factorial
to access it, you can just use Factorial::val
instead of
Factorial factorial;
factorial.val;
The variable is const
because the factorial of a given number is always the same, and because the project won't compile if it isn't const
, because the compiler has no way of knowing if you change the variable someplace else.
The variable has a value of N * Factorial<N - 1::val;
, which basically multiplies N
with the factorial of the previous number. This is because of how factorials are defined (3! = 2! * 3 = 1! * 2 * 3 = 1 * 2 * 3 = 6
).
template <>
struct Factorial <0> {
static int const val = 1;
};
This defines a fully specialized struct
for N = 0
. This is really important, or else the recursion used in the previous function will never stop.
Then, getting the factorial of a number N
is easy, Factorial<N>::val
. This will be calculated at compile time.
This is also Template Metaprogramming
.
template <int ...>
struct my_sum;
This defines an empty template struct
which takes an int...
(a Parameter Pack
), so that it can be specialized (see next point).
template <>
struct my_sum <> {
static const int value {0};
};
This specializes the struct
my_sum
, when no template arguments are given (this is because of the Parameter Pack
, which can be empty, and so the template arguments will be empty when the Parameter Pack
is expanded). The value
is static
and const
because of the same reasons as before, and it is 0
initialized using an initializer list
(for an int
, there is no difference between int i = 0;
and int i{ 0 };
).
template <int i, int ... tail>
struct my_sum <i, tail ...> {
static const int value = i + my_sum<tail ...>::value;
};
This defines the struct
my_sum
as a template which takes 2 template arguments, an int
and a int
parameter pack. This is used to get the value of the first value of a parameter pack, because you can't index a parameter pack (it's not an array). Then, value
is initialized as i
(the first value of the pack) plus the value
of the other values as parameter pack, which is expanded (using the ...
):
int sum = my_sum<1, 2, 3>::value;
This calls my_sum<int i, int... tail>
, i
is 1
and tail
is 2, 3
. value
is i + my_sum<tail...>::value
, so it is 1 + my_sum<2, 3>
. my_sum<2, 3>
calls the same function again, 2 + my_sum<3>::value
. Now we have 1 + 2 + my_sum<3>::value
. my_sum<3>::value
calls again the same function, but now the parameter pack is empty! So value
is 1 + 2 + 3 + my_sum<>::value
. my_sum<>::value
is 0
(as defined) and so value = 1 + 2 + 3 + 0
.
The expression is evaluated, but the program doesn't crash because the expression when evaluated, is a double
. Only when the expression is an int
does it crash with a Integer division by zero exception
. If you were to do this:
int zero = 0;
double d = 1.0 / zero;
Then d
would have the value inf
.
The function some_function
is a template function which takes as template parameter a parameter pack. Then it calls sizeof...
which counts the elements in the parameter pack, and outputs it usingstd::cout
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With