Compile-Time loops
We want to develop a compile-time loop structure that is flexible and allows to print templates and to unroll arbitrary loops index-wise.
The classical example for C++ meta-programming is to define a recursion formula by template parameter specialization. Implementing a meta-program to calculate the Nth Fibonacci number (see Wikipedia) could look like:
The primary template defines the recursion formula and two template-specializations
for I=0 and I=1 introduce break conditions. A static constant value is defined
for the template Fibo, to access the resulting value of the Fibonacci calculations.
Thus, the Nth Fibonacci number can be read by Fibo<N>::value
.
The same can be written using concepts-lite technique (in C++17, Technical specification, Tutorial) by formulating the recursion formula using a requires clause:
Here the order of definition has changed, i.e. at first we have implemented the break conditions and finally the recursion formula for all integer greater than 1. Thus, for negative indices we set the Fibonacci number to 0. To compile this code, currently you have to use the trunk version of gcc (HowTo: build gcc with concepts), with
In order to print all Fibonacci numbers from 0 to 50 one has to write a compile-time loop. By explicitly implementing a print-loop for the Fibo class this can be solved:
It instantiates the template Print<I,N>
from an initial I to the (not included)
upper bound N. Thus, we have to call
to print all Fibonacci numbers from 0 to 50. This loop is specialized for the Fibo class. A more general implementation takes a functor for the output operation instead:
The static functor PrintFibo implements a static method eval, that takes an integer template argument. In order to call this method in a templated Loop class, we have to explicitly specify that the method is templated:
where F corresponds to the PrintFibo class. The printing loop can be instantiated, by
Instead of calling a templated eval method by explicitly specifying the template
parameter, we could implement a classical static functor, that takes an argument
that represents the integral integer parameter, by using a helper class int_
:
This is slightly simpler to use, by passing an instance of the int_<I>
class
to the eval method. So far, the class PrintFibo is passed as template argument.
Thus, we have to use a static eval method in the functor. Classically functors
implement an operator()
to be called. In order to allow (dynamic) functors to
be used in the compile-time loop, we modify the implementation slightly:
by passing the functor as argument to the run()
method.
Now, the loop can be instantiated by
This is much more flexible, but requires a functor that has a templated
operator()
with integer non-type parameter. So, we can not simply use
lambda-expressions as functors. This is a heavy restriction and should be
overcome. In order to do so, we change the implementation of the int_
class
slightly, by adding a constexpr cast operator:
This now allows to use int_<I>
as template parameter for integer arguments and
the loop can be instantiated using a generic lambda, that does not need to know
its template parameter explicitly:
Adding some more tests and the flexibility to iterate forward, backward, or in stepsize greater than 1, and using concepts-lite as a tool for simplified implementation, we arrive at the final version:
Here, we have implemented the break condition with boolean template constraints
for upper and lower bound. Instead of passing the functor by copy to the next
iterate, it is now passed by universal reference. The int_
template is
changed to the standard compatible std::integral_constant
, since this provides
the used cast operator and some more attributes. But also the user-defined int_
class can be used.
The loop can be used as follows:
Output:
Fibo<10>::value = 55
Fibo<9>::value = 34
Fibo<8>::value = 21
Fibo<7>::value = 13
Fibo<6>::value = 8
Fibo<5>::value = 5
Fibo<4>::value = 3
Fibo<3>::value = 2
Fibo<2>::value = 1
Fibo<1>::value = 1
Download Source
You can download the source of the final implementation from here.