Métaprogrammation with owners

The Métaprogrammation with owners is a technique of programming in which the owners are used so that the Compilateur, during the compilation of the code, carries out a program. These programs can generate constants or structures of data. This technique is used mainly in the computer programming language C++.

Calculation of constants

The simple example of calculation of factorial with recursion illustrates well what is the " programming at the time of the compilation".

In C++, a function Factorielle can be written recursively as follows:

int factorial (unsigned int N) 
{
   yew (N == 0)
     return 1;
   return N * factorial (N - 1);
}
 

// factorial (4) == (4 * 3 * 2 * 1) == 24 // factorial (0) == 0! == 1

By using the metaprogrammation with owners, one can write:

template 
struct Factorial 
{
   enum {value = NR * Factorielle:: value};
};
 

template <> struct Factorielle<0> { enum {value = 1}; };

// Factorielle<4>:: value == 24 // Factorielle<0>:: value == 1

The solution of the metaprogrammation with owners uses the specialization of owner to finish recursion. Although these two solutions are similar, in the second case, Factorielle<4>:: valeur is calculated during compilation. That implies that Factorielle< X >:: valeur can ête used only if X is known during compilation, i.e. if X is a constant or the résulat of a call to sizeof () .

In D, the owner factorial would resemble:

template Factorial (unsigned int N) { static if (N == 1) const Factorial = 1; else const Factorial = N * Factorial! (n-1); } // Factorial! (4) == 24

The metaprogrammation with owners has practical uses in spite of its awkward appearance. It can be used to create classes vector with N dimensions when N is known with compilation. The advantage compared to a vector with N dimensions traditional is that the loops can be unrolled, which produces a very optimized code.

Let us consider this example of the operator of addition. An addition for a vector with N dimensions could be written as follows:

template
Vector& Vector:: operator+= (const Vector& rhs) 
{
   for (int I = 0; I < dimension; i++)
     been worth += rhs.value;
   return *this;
}

When the compiler instancie the function owner defined above, the following code is produced:

template<>
Vector<2>& Vector<2>:: operator+= (const Vector<2>& rhs) 
{
   been worth += rhs.value;
   been worth += rhs.value;
   return *this;
}

The optimizer of the compiler is able to unroll the loop for because the parameter dimension of the owner is a constant known with compilation. For a true implementation of this technique, to see.

In C++, the metaprogrammation with owners is Turing-complete, which means that any calculation exprimable by a program can be calculated by a métaprogramme containing owners.

The métaprogrammes containing owners do not have variable mutable, i.e. no variable can once change value it was initialized. That has as a consequence that contrary to C++ with the execution, the metaprogrammation containing owners is a form of functional Programmation. For this reason, the Analyze of flood in the métaprogrammes is made by the means of recursion, as considering higher

Initialization of data

In C++, the metaprogrammation with owners makes it possible to initialize a data like a table or a complex structure of data. The following example illustrates the filling of a table whose 32 values are the square of the index, and the following values are the index.

The template is used to make calculate the value of an element of the table: template struct ValeurTab { enum {value = NR > 32? NR: NR * NR}; }; As in the example of factorial, to recover the 10th value, it is enough to write ValeurTab<10>:: valeur Thus the filling of a table would be done as follows: int = {ValeurTab<0>:: value, ValeurTab<1>:: value, ValeurTab<2>:: value/*… *}

To facilitate the filling of a large table, one can use traditional macros: #define REMPLIR_TAB1 (I) ValeurTab< (I) >:: value #DEFINE REMPLIR_TAB2 (I) REMPLIR_TAB1 ((I)), REMPLIR_TAB1 (I + 1) #DEFINE REMPLIR_TAB4 (I) REMPLIR_TAB2 (I), REMPLIR_TAB2 (I + 2) #DEFINE REMPLIR_TAB8 (I) REMPLIR_TAB4 (I), REMPLIR_TAB4 (I + 4) #DEFINE REMPLIR_TAB16 (I) REMPLIR_TAB8 (I), REMPLIR_TAB8 (I + 8) #DEFINE REMPLIR_TAB32 (I) REMPLIR_TAB16 (I), REMPLIR_TAB16 (I + 16) #DEFINE REMPLIR_TAB64 (I) REMPLIR_TAB32 (I), REMPLIR_TAB32 (I + 32) #DEFINE REMPLIR_TAB128 (I) REMPLIR_TAB64 (I), REMPLIR_TAB64 (I + 64) //.. Thus to fill a table with 100 elements by respecting the algorithm of the owner of class, it is enough to write: int = {REMPLIR_TAB64 (0), REMPLIR_TAB32 (64), REMPLIR_TAB4 (64 + 32)};

Control on compilation

Initially, some classes: class UneClasseA { public: UneClasseA () {} virtual ~UneClasseA () {} }; class UneClasseB: UneClasseA public { public: UneClasseB () {} virtual ~UneClasseB () {} }; class UneClasseC: UneClasseA public { public: UneClasseC () {} virtual ~UneClasseC () {} }; class UneNouvelleClasseD: UneClasseA public { public: UneNouvelleClasseD () {} virtual ~UneNouvelleClasseD () {} };

It may be that, in a program, an object of class UneClasseC is handled, at the time of various calls of functions (or methods), like an object of class UneClasseA in order to benefit from the advantages of polymorphism. However, it may be that have it has a specific treatment according to the real class of the object. For example, This frequently arrives for the graphic posting of an object when the program is cut out according to the concept MVC. This technique is a reason for design called visitor. Classically, in C++, that is made by a downward coercion ( Downcasting in English). It is then enough to make the tests: UneClasseB* pConvB = 0; UneClasseC* pConvC = 0; yew (pConvB = dynamic_cast (pObjetA)) { // Treatment specific to uneClasseB } else yew (pConvC = dynamic_cast (pObjetA)) { // Treatment specific to uneClasseB } else { // Error, unknown type } A difficulty can arise during the programming of new object (UneNouvelleClasseD). Indeed, it is necessary to think of modifying all the parts of codes which make this kind of treatment. The error will be visible only during the execution of the program, and it is necessary that this execution passes in this portion of code to observe the error.

Here, the metaprogrammation makes it possible to make the program impossible to compile if one adds a type which is not treated nowhere.

For that, it is necessary to draw up a list of the types which will make it possible to check if all the treatments of each type were programmed. template struct ListeType {}; struct ListeFin {}; a list of the types will be presented in a recursive form: a list contains a type and a sublist. The list ends in the ListeFin type. For example: ListeType >

It is then enough to create an abstract class which will be specialized with this list of the types. This class will be made nonabstract while implementing a pure virtual method for each type (methods by specific treatment). Thus, the lapse of memory of the implementation of the method of a type of the list, will generate an abstract class. An abstract class not being able to be instanciée, the compiler will turn over in error during the creation of the Visiteur object. It will then be enough to add the implementation of the method of treatment to the new type, to remove the abstract behavior of the class and to allow an instanciation. Thus, the compiler can then compile the program and no method of implementation will be forgotten.

It is thus necessary to create an abstract class visitor with an unspecified list of types: template class AbsVisiteur; // Specialization of AbsVisiteur for the case where it is a list of the ListeType type // Inherits AbsVisiteur of the sublist to create the tree of heritage so // to go back to the relative to launch the visit template class AbsVisiteur< ListeType >: AbsVisiteur public { public: // pure virtual Method which makes the class abstract virtual void visit (H*) = 0; template void lanceVisite (Z* pZ) { H* pConv = 0; yew (pConv = dynamic_cast (pZ)) { // the type running (in the list) is that of the current class // (in the tree of heritage) visit (pConv); } else { // the type running (past with the function) is different from that from // standard running (in the list and the tree heritage) // the sublist is tested with the class relationship AbsVisiteur:: lanceVisite (pZ); } } }; // Specialization for the type of end of list // It is the super-class class AbsVisiteur< ListeFin > { public: template void lanceVisite (Z* pZ) { // This class is the class mother of all the classes corresponding // with the last type of the list (standard which makes it possible to finish the list). // Thus, here, all the list was tested, and no corresponding class // with the type of the function was not found. // Thus ERROR the type given is not in the traversed list throw " Introuvable" type; ; } }; Then, it is necessary to create the list of the specific types. typedef ListeType > UneListeClasses;

Lastly, it is necessary to create the class which will implement all the specific treatments: class Visitor: AbsVisiteur public { public: virtual void visit (UneClasseB * b) { // Treatment specific to uneClasseB } virtual void visit (UneClasseC * c) { // " Treatment specific to uneClasseC } // If it misses the implementation of a class of the list, the class remains // abstract because it remains a pure virtual method Visit () = 0 }; Thus, to add a new type, it should be added in the list of the UneListeClasses types, and so that the program compiles, it will be necessary to add the implementation of the method visite for the new type.

Advantages and disadvantages of the metaprogrammation containing owners

  • Compromised between compilation and the execution : as all the code in the form of owner is treated, evaluated and generated with compilation, compilation will take more time while the achievable code could be more effective. Although this addition is generally tiny, for large projects, or projects which use owners in great quantity, that can be important.

  • generic Programming : the metaprogrammation containing owners makes it possible to the programmer to concentrate on architecture and to delegate to the compiler the generation of any implementation required by the code customer. Thus, the metaprogrammation containing owners can be accomplished via really generic code, facilitating the minimization and the maintainability of the code.
  • Legibility : the syntax and the idioms of the metaprogrammation containing owners are esoteric compared to of conventional C++, and of the advanced métaprogrammes can be very difficult to include/understand. The métaprogrammes can thus be difficult to maintain for programmers little broken with this technique.
  • Portability : because of differences in the compilers, the code strongly resting on metaprogrammation containing owners (in particular new forms of metaprogrammation) can have preoccupations with a portability.

Random links:Presumption of innocence | Couzou | Armonie Sanders | Route main road 85a | He' S Bonkers | 8_Flora