C++ : Un peu de méta-programmation C++ : Compter le nombre d’objets
Calcul de constante template<typename T>
struct Compteur {
Calcul sur les types
static unsigned cp;
Vérification de concept protected:
Compteur() { ++cp; }
Polymorphisme statique
Compteur(const Compteur& c) { ++cp; }
Liste de types ~Compteur() { --cp; }
};
Hiérarchie automatique
template<typename T>
Manipulation de vecteurs unsigned Compteur<T>::cp=0;
struct A : public Compteur<A> { /*..*/ };
1 2
C++ : Calcul de constantes C++ : Calcul de constantes
Grâce aux classes template, on peut faire des calculs récursifs sur On peut vérifier que la constante est bien calculée au moment de
les constantes. la compilation.
template<unsigned N> template<unsigned N>
struct NbBits { struct Aff {};
static const unsigned res = 1+NbBits<N/2>::res;
}; int main() { Aff< NbBits<273>::res >::print; }
Lorsqu’on compile avec g++ on obtient
template<>
struct NbBits<0> { error : ’print’ is not a member of ’Aff<9u>’
static const unsigned res = 0;
};
3 4
C++ : Calcul de constantes C++ : Tester l’égalité de deux types
Attention, le code suivant n’est pas correct: template<typename T, typename U>
template<unsigned N> struct AreEquals { // Définition
struct NbBits { static const bool val= false;
static const unsigned res = };
(N==0) ? 0 : (1+NbBits<N/2>::res); template<typename T>
}; struct AreEquals<T,T> { // Spécialisation
static const bool val= true;
En effet, pour pouvoir évaluer l’expression, le compilateur évalue
toutes les sous-expressions, d’où un récurrence infinie. };
template<typename T, typename U>
void truc(const T& x, const U& y) {
if(AreEquals<T,U>::val) //...
}
5 6
C++ : Vérification de concept C++ : Vérification de concept
On peut écrire un type template pour vérifier un concept. Par Pour vérifier si un type truc_t vérifie le concept, il suffit d’écrire
exemple, pour s’assurer qu’un type possède {CheckConcept<truc_t>::check;}.
– une méthode static void make(int); et Si la classe truc_t ne possède pas les méthodes dont le type
– une méthode objet int get() const: est celui des pointeurs requis en paramètres de CheckConcept,
On écrit la classe suivante: le compilateur affiche une erreur.
template<typename T, error: ‘get’ is not a member of ‘truc_t’
void (*Make)(int) = &T::make, error: template argument 3 is invalid
int (T::*Get)() = &T::get>
struct CheckConcept{
static const int check=0;
};
7 8
C++ : Rappel : Polymorphisme dynamique C++ : Rappel : Polymorphisme dynamique
Rappel: de manière dynamique, le polymorphisme s’obtient grâce struct A {
à virtual. virtual int norm() { /***/ }
int binorm() { return 2*norm(); }
A
};
virtual norm():int
binorm():int
struct B : public A {
int norm() { /***/ }
};
B b;
B //...
norm():int [Link](); // Calculé avec norm() de B
9 10
C++ : Polymorphisme statique C++ : Polymorphisme statique
On voudrait un comportement similaire, avec des appels de template<typename T>
méthodes résolus à la compilation. struct Self{
public:
Pour cela, on fera en sorte que chaque classe soit paramétrée par
typedef T type;
le vrai type de l’objet. Ainsi, la classe B est passée en paramètre type& self() {return static_cast<type&>(*this);}
de la classe qu’elle étend. const type& self() const {
struct B : public A<B> { return static_cast<const type&>(*this); }
int norm() { /***/ } };
}; template<typename T>
struct A : public Self<T> {
int norm() { /***/ }
int binorm() { return 2*this->self().norm(); }
};
11 12
C++ : Polymorphisme statique C++ : Polymorphisme statique
La solution proposée fonctionne bien si on veut simuler une classe Première solution:
abstraite A. Mais sinon, on ne peut pas instancier d’objet de type template<typename T>
A, puisqu’il s’agit d’une classe template. . . struct A_: public Self<T> {
int norm() { /***/ }
int binorm() { return 2*this->self().norm(); }
};
struct A : public A_<A> {};
struct B : public A_<B> {
int norm() { /***/ }
};
13 14
C++ : Polymorphisme statique C++ : Polymorphisme statique
On peut même étendre ce principe à toutes les classes de la A B
Self Self
hiérarchie: self():A self():B<NullType>
template<typename T>
struct B_ : public A_<T> { A B
A_ A_
int norm() { /***/ }
norm():int norm():int
}; binorm():int binorm():int
B
struct B : public B_<B> {}; A B_
norm():int
15 16
C++ : Polymorphisme statique C++ : Polymorphisme statique
Seconde solution: On peut écrire une classe qui fait ce calcul:
template<template<typename> class A, typename T>
On crée un type NullType pour paramétrer un type qu’on struct TrueType {
souhaite instancier. typedef T type;
};
struct NullType {};
Le type A<T> représente alors un objet dont le vrai type est T, template<template<typename> class A>
sauf si T=NullType, auquel cas le vrai type est A<NullType>.
struct TrueType<A,NullType> {
typedef A<NullType> type;
};
17 18
C++ : Polymorphisme statique C++ : Polymorphisme statique
La classe A s’écrit alors : On peut étendre ce principe à toutes les classes de la hiérarchie:
template<typename T=NullType> template<typename T=NullType>
struct A : struct B :
public Self<typename TrueType<A,T>::type > { public A<typename TrueType<B,T>::type > {
int norm() { /***/ } int binorm() { return 2*this->self().norm(); }
int binorm() { return 2*this->self().norm(); } };
};
Et on peut instancier un objet de type A en écrivant A<> a;
19 20
C++ : Polymorphisme statique C++ : Liste de types
A<NullType> B<NullType>
Self Self struct NullType{};
self():A<NullType> self():B<NullType>
template<typename H, typename T>
struct TypeList{
NullType B<NullType>
typedef H head;
A A typedef T tail;
norm():int
binorm():int
norm():int
binorm():int
};
typedef
NullType
TypeList<int, TypeList<double, NullType> >
B liste_t;
norm():int
21 22
C++ : Liste de types C++ : Liste de types
template<typename List> template<typename List, typename T>
struct ListLenght{ struct ListAdd {
static const int value = typedef TypeList<
1+ListLenght<typename List::tail>::value; typename List::head,
}; typename ListAdd<typename List::tail, T>::t
> type;
template<> };
struct ListLenght<NullType>{
static const int value = 0; template<>
}; struct ListAdd<NullType, T>{
typedef TypeList<T, NullType> type;
};
23 24
C++ : Créer des listes C++ : Créer des listes
On peut utiliser des fabriques pour faciliter la création des listes de template<typename T1, typename T2=NullType,
types. typename T3=NullType, typename T4=NullType>
struct MakeList {
typedef TypeList<T1,
typename MakeList<T2,T3,T4,NullType>::type
> type;};
template<>
struct MakeList<NullType,NullType,
NullType,NullType> {
typedef NullType type; };
25 26
C++ : Créer des listes C++ : Hiérarchie automatique
On peut aussi écrire des classes pour concaténer les listes, On peut grâce aux templates et aux listes de types engendrer
supprimer les doublons, etc.
automatiquement une hiérarchie.
Le but peut être
de créer un type qui étend tous les types de la liste;
d’avoir une méthode définie pour chaque type de la liste
27 28
C++ : Hiérarchie automatique C++ : Hiérarchie automatique
template<typename T> template< typename TList,
struct MonHandler { template <typename> class Handler>
bool ma_methode(const T& t) const { /****/ } struct AutomaticHierarchy :
}; public Handler<typename TList::head>,
public AutomaticHierarchy<typename TList::tail,
Handler> {};
template<template <typename> struct Handler >
struct AutomaticHierarchy<NullType, Handler> {};
29 30
C++ : Hiérarchie automatique C++ : Hiérarchie automatique
struct A : double NullType, MonHandler
AutomaticHierarchy<MakeList<int,double>::type, MonHandler AutomaticHierarchy
MonHandler> {
/****/
}; int [double], MonHandler
MonHandler AutomaticHierarchy
[int,double], MonHandler
AutomaticHierarchy
31 32
C++ : Hiérarchie automatique C++ : Hiérarchie automatique
Un autre type de hiérarchie automatique est celle qui permet template<unsigned N, typename T>
d’hériter plusieurs fois de la même classe. struct No : T{};
On veut par exemple implanter la composition d’un objet de type
Voiture avec 4 objets de type Roue, ce qui se fait en C++ via template<unsigned N, typename T>
l’héritage privé. struct Hierarchy : No<N,T>, Hierarchy<N-1, T> {};
template<typename T>
struct Hierarchy<0,T>{};
struct Voiture :
private Hierarchy<4,Roue> {
/****/
};
33 34
C++ : Hiérarchie automatique C++ : Hiérarchie automatique
Roue On pourra ensuite appeler la méthode truc de la troisième roue
Roue avec this->No<3,Roue>::truc(...).
1,Roue 0, Roue
No Hierarchie
4, Roue
No
4, Roue
Hierarchie
Voiture
35 36
C++ : Expressions template C++ : Expressions template
On veut écrire une bibliothèque Vector qui permette d’écrire, par La solution consiste à faire en sorte que( V+W )*5 - U
exemple construise un arbre d’itérateurs;
Vector U,V,W,X;
chaque feuille est un itérateur sur le vecteur qui est un atome de
//...
l’expression,
X = ( V+W )*5 - U;
chaque nœud interne correspond à un opérateur arithmétique, il
Si on surcharge classiquement les opérateurs pour les vecteurs,
fait avancer ses fils avec lui et son déréférencement consiste à
chaque sous-expression nécessite un parcours linéaire et la
appliquer l’opérateur au déréférencement de ses fils.
création d’un objet temporaire.
L’opérateur d’affectation appliqué à cet arbre provoque le parcours
Comment faire en sorte qu’un tel calcul se fasse avec un seul qui permet de renseigner le résultat.
parcours et sans objet temporaire?
37 38
C++ : Expressions template C++ : Expressions template
struct Vector //Ce qui est commun à tous les noeuds binaires
{ template <class T, class U> struct BinaryOpBase
double Elements[3]; {
typedef double* iterator; BinaryOpBase(T I1, U I2) : It1(I1), It2(I2) {}
typedef const double* const_iterator; inline void operator ++() {++It1; ++It2;}
template<typename T>
const Vector& operator=(T t); T It1;
iterator begin() {return Elements;} U It2;
iterator end() {return Elements + 3;} };
const_iterator begin() const {return Elements;}
const_iterator end() const {return Elements+3;}
};
39 40
C++ : Expressions template C++ : Expressions template
//Pour chaque noeud, on fixe le déréférencement template <class T, class U>
template <class T, class U> AddOp<T, U> operator +(const T& v1, const U& v2)
struct AddOp : public BinaryOpBase<T, U> {
{ return MakeAdd(v1, v2);
AddOp(T I1, U I2) : }
BinaryOpBase<T, U>(I1, I2) {} AddOp<Vector::const_iterator,
inline double operator *() { Vector::const_iterator>
return *It1 + *It2;} operator +(const Vector& v1, const Vector& v2)
}; {
template <class T, class U> return MakeAdd([Link](), [Link]());
inline AddOp<T, U> MakeAdd(const T& t, const U& u) }
{ return AddOp<T, U>(t, u); } //et versions (const Vector& v1, const T& v2)
et (const T& v1, const Vector& v2)
41 42
C++ : Expressions template
Il suffit alors de surcharger l’opérateur d’affectation.
template <class T>
const Vector& Vector::operator =(T Expr)
{
for (iterator i = begin(); i != end();
++i, ++Expr)
*i = *Expr;
return *this;
}
et voilà !
43