Hell .pdf



Nom original: Hell.pdf

Ce document au format PDF 1.5 a été généré par LaTeX with hyperref package / pdfTeX-1.40.14, et a été envoyé sur fichier-pdf.fr le 29/11/2014 à 20:50, depuis l'adresse IP 176.184.x.x. La présente page de téléchargement du fichier a été vue 2468 fois.
Taille du document: 1.7 Mo (40 pages).
Confidentialité: fichier public




Télécharger le fichier (PDF)










Aperçu du document


Voyage en enfer

Thomas Sanchez koala@epitech.eu

Abstract: Vous faites maintenant du C++ depuis quelque temps. Il y a plusieurs types de réactions face au C++, les gens qui le haïssent et qui ne l’utilisent pas et ceux qui le haïssent et qui
l’utilisent.
C’est un langage très complexe, certainement le plus complexe que vous aurez à utiliser. Si
vous apprenez à le comprendre, vous pourrez en faire ce que vous voudrez tout en connaissant ses
limitations ou ses bizarreries. Vous allez rencontrer des problèmes bizarres tout au long de votre
apprentissage, mais tous s’expliquent plus ou moins et trouvent leurs justifications.
Ce que je vous propose au long de ce rush, c’est une descente aux enfers avec moi afin de
démystifier ce qui m’avait paru impossible et peut être aussi à vous.
Bonne chance. . .

Table des matières
I

Sur la barque de Charon
I.1
Étape 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
I.2
Étape 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
I.3
Arrivée . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

II

Le purgatoire
II.1
Étape 1 . . .
II.2
Étape 2 . . .
II.3
Étape 3 . . .
II.4
Étape 4 . . .
II.4.1 Travail .
II.5
Étape 5 . . .
II.5.1 Travail .
II.6
Étape 6 . . .
II.6.1 Travail .
II.7
Étape 7 . . .
II.8
Étape 8 . . .
II.8.1 Travail .
II.9
Étape 9 . . .
II.10
Le Jugement

III

3
5
6
7

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

8
10
12
12
13
16
17
18
22
22
22
23
23
24
24

L’appel du Diable
III.1
Un mot sur les templates . . . . . . .
III.1.1 Le sizeof . . . . . . . . . . . . . .
III.1.2 SFINAE . . . . . . . . . . . . . . .
III.1.3 Exemple . . . . . . . . . . . . . .
III.1.4 Travail . . . . . . . . . . . . . . .
III.2
L’antichambre . . . . . . . . . . . . .
III.3
Étape 1 ou les effluves du démon . . .
III.4
Étape 2 ou la désorientation maléfique
III.5
Étape 3 ou la métamorphose . . . . .
III.6
Étape 4 ou comment devenir fou . . .
III.7
Étape 5 ou la torture mentale . . . . .
III.8
Étape 6 ou le suplice utilme . . . . . .
III.9
Étape 7 ou la paix . . . . . . . . . . .

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.

25
25
25
26
26
27
30
30
31
31
37
37
37
37

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.

IV

Consignes générales

38

V

Consignes de rendu

39

1

Voyage en enfer

Figure 1 – Le C++

2

Chapitre I
Sur la barque de Charon
C’était un de ces sons impies, abominables, que rien ne saurait décrire. Parler
d’un gémissement morne et sans âme, d’un hurlement d’épouvante poussé par
un choeur de damnés, ne suffirait pas à exprimer sa hideur quintessentielle.
L’affaire Charles Dexter Ward - Lovecraft

3

Voyage en enfer

Nous allons commencer notre descente doucement. Depuis le début des langages interprétés, la plus grosse et la plus utile des fonctionnalités reste celle des foncteurs. Cette fonctionnalité a été introduite pour la première fois en LISP (langage spécifié par McCarthy en
1958).
Le C++, qui lui a été commencé en 1979, n’a que ses pauvres pointeurs sur fonction ou
méthode.
Soit, nous allons faire avec, embarquez. . .

Figure I.1 – Sur la barque, en direction de l’enfer

4

Voyage en enfer

I.1

Étape 1

Les templates C++ sont ce qu’ils sont, mais ils nous permettent de faire quelques trucs
amusants. Une des choses qui m’avaient étonné était le fait de pouvoir noter la signature
d’une méthode dans un template. C’est-à-dire qu’il est possible d’écrire :
FunctionSignature<int (const std::string& str)>::type f = &onefunction;
f("coucou");

Vous devez écrire une structure nommée FunctionSignature, qui prend en template une
signature de fonction jusqu’à 4 arguments. Le type devra être accessible à travers le typedef
type.
Vous devez vous demander comment faire ?
1. Déclarez une structure qui prend un template ;
2. Déclarez la meme structure en spécialisant partiellement la précédente.
La spécialisation aura la forme de la signature de la fonction alors que la partie déclaration
de type template déclarera chacun des membres de la signature.
Demandez vous quelle est la partie déclaration et la partie spécialisation.
C’est vague ? Bien, et ça ne fait que commencer. . .
Rendu
Le rendu devra se faire dans le fichier : <racine du dépôt>/charon/etape1/FunctionSignature.hpp
Vous devrez rendre un fichier de tests complet : <racine du dépôt>/charon/etape1/main.cpp
Le nom du binaire devra être test_functor_type, généré dans le dossier etape1.
Ce qu’on attend de vous :

$> ls && mkdir build && cd build && cmake .. && make && cd .. && ls
CMakeLists.txt
FunctionSignature.hpp main.cpp
-- The CXX compiler identification is GNU
-- Checking whether CXX compiler has -isysroot
-- Checking whether CXX compiler has -isysroot - yes
-- Checking whether CXX compiler supports OSX deployment target flag
-- Checking whether CXX compiler supports OSX deployment target flag - yes
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Configuring done
-- Generating done
-- Build files have been written to: /Users/daedric/work/rush_cpp/hell/charon/etape1/b
Scanning dependencies of target test_functor_type
[100%] Building CXX object CMakeFiles/test_functor_type.dir/main.o
Linking CXX executable ../test_functor_type
5

Voyage en enfer

[100%] Built target test_functor_type
CMakeLists.txt
FunctionSignature.hpp build
test_functor_type

main.cpp

Astuce
EXECUTABLE_OUTPUT_PATH
Notation
Si ça ne marche pas, 0 à l’exercice, 1 point de bonus si les surcharges sont autogénérées

I.2

Étape 2

Vous devez recoder un foncteur.
On devra pouvoir écrire :
Function<int (char)> f = &function;
f(’c’);

Ou même :
Function<int (char)> f = boost::bind(&f, _1);
f(’c’);

Pour commencer, vous devez vous demander quelles sont les étapes afin de mener à bien
cet exercice. D’après les exemples, on peut en déduire qu’on va avoir une classe template
Function qui sera surchargée et qui pourra prendre n’importe quoi qui est appelable via la
surcharge d’opérateur "()" ou alors un objet de type pointeur sur fonction : Function aura
donc un constructeur prenant un template en paramètre et son operateur "=" sera surchargé
afin de pouvoir remplacer le foncteur contenu.
Or, ce template n’est pas donné dans la liste lors de la définition de Function.
Donc le problème sera : comment garder la trace d’un type connu qu’a la compilation
dans une classe et l’appeler lorsqu’on appelle ma surcharge d’opérateur "()".
Indice : Classe interne, interface. Avez-vous besoin de connaitre le type qu’on vous a
donné à la construction ?
Si le bonus de la génération de code a déjà été fait, il ne sera pas re-comptabilisé en tant
que bonus. Certains autres bonus pourraient être possibles ici, mais ne perdez pas de temps
dessus. Vous avez compris le principe, c’est le principal. Le barème fera de même.
Rendu
Le rendu devra etre effectué dans le fichier : <racine du dépôt>/charon/etape2/Function.hpp
Un fichier main.cpp devra être présent et le CMakeLists.txt devra générer un binaire
test_function dans le répertoire des sources. Ces tests devront comprendre au moins :

6

Voyage en enfer
• Pointeur sur fonction.
• boost::bind sur fonction/méthode.
• callable (objet redéfinissant l’opérateur parenthèse).

I.3

Arrivée

Bravo, vous avez survécu au voyage. Maintenant, allez-vous pouvoir traverser le purgatoire ?

7

Chapitre II
Le purgatoire
C’était le reflet vampirique de la pourriture, des temps disparus et de la désolation ; le phantasme, putride et gras d’égouttures, d’une révélation pernicieuse
dont la terre pitoyable aurait dû pour toujours masquer l’apparence nue. Dieu
sait que cette chose n’était plus de ce monde - et pourtant au sein de mon effroi,
je pus reconnaître dans sa matière rongée, rognée, où transparaissaient des os,
comme un grotesque et ricanant travesti de la forme humaine. Il y avait, dans cet
appareil pourrissant et décomposé, une sorte de qualité innommable qui me glaça
encore plus.
Je suis d’ailleurs - Lovecraft
Vues des contreforts, ses pentes noires et couvertes de ruines se dressaient sur
l’est, escarpées et hideuses, nous rappelant une fois de plus les étranges peintures
asiatiques de Nicholas Rœrich ; et quand nous pensâmes aux abominables dédales
qu’elles recelaient et aux terrifiantes entités informes qui pouvaient avoir poussé
l’avance de leur bave fétide jusqu’au faîte des cimes creuses, nous ne pûmes envisager sans panique la perspective de voler de nouveau près de ces impressionnantes cavernes ouvertes vers le ciel où le vent sifflait comme la flûte sauvage
et sa large gamme. Pour aggraver les choses, nous vîmes des traces distinctes de
brumes locales autour de plusieurs sommets – comme le malheureux Lake l’avait
fait sans doute lors de sa première erreur sur le volcanisme – et nous évoquâmes
en frissonnant la brume semblable à laquelle nous venions d’échapper ; cela et
l’abîme maudit, générateur d’horreur d’où sortaient de telles vapeurs.
Les montagnes hallucinées - Lovecraft

8

Voyage en enfer

Figure II.1 – Le purgatoire

9

Voyage en enfer

Vous venez d’entrer dans le purgatoire. La première chose qui vous frappe c’est le monde
qu’il y a. Vous vous demandez bien comment ils peuvent trier les "bonnes" des "mauvaises"
personnes. . . Alors vous commencez à imaginer comment vous pourriez trier toutes ces âmes. . .

II.1

Étape 1

Le mal a plusieurs visages, c’est bien connu, mais dans un premier temps nous allons nous
intéresser à un seul motif. Ce motif-là devra être recherché dans l’essence de l’âme.
L’essence est assimilable à une chaine de caractères ASCII, qui se suivent sans forcément
de limite, et lorsqu’une séquence du mal est reconnue vous devrez notifier une personne.
Comme il y a du monde dans le purgatoire et que vous avez le temps, vous n’allez pas
vous arrêter sur une simple recherche de sous-chaine dans une chaine. C’est le moment de
découvrir les machines à état.
La définition d’une machine à état est simple :
• Un ensemble S d’états ;
• Un alphabet E ;
• Un ensemble d’états finaux Q ;
• Un graphe de transition P .
Prenons un ensemble d’états S. S0 étant l’état initial, S7 l’état final.
Voici les conditions pour passer d’un état à un autre :
m

S0 −
→ S1
e
− S2
S1 →
c
− S3
S2 →
h
S3 →
− S4
a
S4 →
− S5
n
S5 →
− S6
t
S6 →
− S7
On peut en tirer deux tableaux differents :
État
S0
S1
S2
S3
S4
S5
S6
S7

m
S1
Error
Error
Error
Error
Error
Error
Error

e
Error
S2
Error
Error
Error
Error
Error
Error

c
Error
Error
S3
Error
Error
Error
Error
Error

h
Error
Error
Error
S4
Error
Error
Error
Error

a
Error
Error
Error
Error
S5
Error
Error
Error

n
Error
Error
Error
Error
Error
S6
Error
Error

t
Error
Error
Error
Error
Error
Error
S7
Error

Other
Error
Error
Error
Error
Error
Error
Error
Error

Figure II.2 – Tableau de condition d’un passage d’un état à un autre.
10

Voyage en enfer
État
S0
S1
S2
S3
S4
S5
S6
S7

m
MA
Error
Error
Error
Error
Error
Error
HR

e
Error
MA
Error
Error
Error
Error
Error
HR

c
Error
Error
MA
Error
Error
Error
Error
HR

h
Error
Error
Error
MA
Error
Error
Error
HR

a
Error
Error
Error
Error
MA
Error
Error
HR

n
Error
Error
Error
Error
Error
MA
Error
HR

t
Error
Error
Error
Error
Error
Error
MA
HR

Other
Error
Error
Error
Error
Error
Error
Error
HR

Figure II.3 – Tableau d’action à faire en fonction de l’état actuel et le caractère lu.
L’action MA signifie "MOVE APPEND", c’est-à-dire que le caractère lu doit être ajouté au
token construit. L’action HR signifie "HALT RESET", c’est-à-dire qu’un token a été lu complètement. Une réinitialisation du contexte de la machine à état a lieu en général après un HALT
RESET (si l’on décide de continuer la consommation du flux.).
Utiliser une machine à états se fait en trois mouvements :
1. On lit un caractère en entrée ;
2. À partir de ce caractère, on détermine l’état suivant et l’action à effectuer pour passer
a cet état suivant ;
3. On effectue l’action.

Travail
Le premier travail demandé sera d’implémenter une machine à état.
Dans un fichier : <racine du dépôt>/purgatory/etape1/Machine.hpp
Vous définirez 2 énumérations : eState et eAction. Dans eState vous devrez définir les
énumérations de S0 jusqu’à S7 , une entrée spéciale STATE_ERROR devra être ajoutée à la
fin. Dans eAction, nous devrons pouvoir retrouver MA, HR et ACTION_ERROR. Deux variables
globales gStateTable et gActionTable devront être définies (dans le header, extern. . . ). À
vous de deviner le type. . .
Conseil : Ne cherchez pas à optimiser la machine à états pour le moment. gStateTable[S0 ][0]
== S1 ; gActionTable[S2 ][2] == MA; Les tableaux auront STATE_MAX et EDGE_MAX en
tant que taille.
Dans un fichier : <racine du dépôt>/purgatory/etape1/Machine.cpp
Vous devrez inclure le fichier Machine.hpp et implémenter et initialiser les deux variables
globales définies précédemment. Ensuite, vous devrez faire un programme (toujours dans le
même dossier) et indiquer si la première chaine passée en paramètre au programme contient
la chaine définie par la machine. Lors de la correction, nous testerons avec notre fichier Machine.cpp, votre programme devra quand même marcher.

11

Voyage en enfer

Il y a un petit souci cependant, c’est que pour que nos tableaux soient interchangeables
on a besoin d’avoir le même alphabet.
Donc pour des raisons de simplicité, l’alphabet ne changera pas entre les tables de corrections et la vôtre. Prenez bien les tableaux dans cet ordre.
Le programme devra consommer toute la chaine ET afficher chaque token consommé.
Rendu
En plus des directives précédente, le programme devra s’appeler : test_fsa.

II.2

Étape 2

Chez les gros méchants, l’essence de l’âme même est altérée ! Reprenez votre programme
précédent, et modifiez les tableaux pour arriver à détecter la chaine "meeeeeechant" avec un
nombre potentiellement illimité de ’e’.
Indice :

un état peut se référencer lui-même.

Rendu
Comme le précédent, mais dans le dossier : <racine du dépôt>/purgatory/etape2

Vous DEVEZ respecter les noms et identifiants imposes, le bareme en
tiendra compte.

II.3

Étape 3

La machine à état fait bien son travail, mais elle est un peu trop statique. Il faudrait arriver
à la générer !
Travail
Créez une classe Edge :
• Se construit avec un caractère.
• On devra pouvoir le comparer avec d’autres.
• On devra pouvoir le stocker dans une collection (vecteur, liste...)
• La méthode operator()(char c) devra renvoyer un booléen si jamais ’c’ est égal au
caractère stocké dans la classe.
Créez une classe State :

12

Voyage en enfer
• On devra pouvoir nommer un état et comparer deux états (via son nom) ;
• On devra pouvoir stocker un état dans une collection (vecteur, liste...) ;
• On devra pouvoir noter un état comme étant final ;
• Une méthode statique devra être présente dans cette classe afin de créer un état avec
un nom unique : Au premier appel, on aura un état S0, au second S1, etc ;
• Une méthode afin d’ajouter un lien vers un autre état à travers un Edge donné devra être implémentée (conseil : stockez le nom et non pas une référence sur l’état,
std::map c’est bien !).
Créez une classe FSA 1 :
• On devra pouvoir y ajouter des états ;
• On devra pouvoir définir un état initial ;
• On devra pouvoir écrire : fsa_instance[state_name] afin de récupérer une référence (tout sauf une copie en fait) sur l’état portant le nom state_name.
Créez une classe Matcher (c’est là que la génération des tableaux prend sa place) :
• Elle prendra une référence sur un FSA ;
• Elle aura une méthode find qui prendra une chaine de caractère en paramètre et renverra un booléen. Elle prendra aussi une référence facultative sur un entier afin de
savoir combien de fois un motif est trouvé dans une chaine de caractères.
Pour cette étape vous pouvez choisir de recréer les tableaux. C’est un peu fastidieux et
c’est du code ingrat. Vous devez avoir compris comment elle se génère et de ce fait vous
devriez être capable de déterminer quel est l’état suivant et quelle est l’action suivante à effectuer sans tableaux. Juste avec l’état courant et le caractère lu.
Créez un main de test qui prouve votre travail. Celui-ci pourra être modifié en soutenance.
Vous serez aussi noté sur votre capacité à faire des tests qui sont les plus complets possible !
Ne négligez surtout pas cette partie car le barème en tient compte !
Rendu
Dans le dossier : <racine du dépôt>/purgatory/etape3
Binaire : test_dynamic_fsa

II.4

Étape 4

Maintenant que vous avez une génération de machine à état automatisée, vous vous apercevez qu’il y a encore quelques limitations. Par exemple, vous ne pouvez pas trouver un
1. Finite State Automata

13

Voyage en enfer

"criminel" et un "mechant" avec la même machine à état.
Comment implémenter ça ?
Il suffit de rajouter des chemins avec des chemins qui acceptent automatiquement le passage d’un état à l’autre. Appelons-les, les LAMBDA-edges.

Vous les trouverez également sous le nom d’Epsilon-transitions sur le
net.

Voici un exemple (le nom des états est totalement arbitraire) :

14

Voyage en enfer

Figure II.4 – Les lambda-edges
Il y a cependant une contrepartie à utiliser les LAMBDA-EDGE. Il est très compliqué de
15

Voyage en enfer

faire une machine à état dessus : la machine à état a besoin d’être déterministe. C’est-à-dire
que pour chaque état, elle ne doit pas avoir de chemin dupliqué vers deux états différents, ni
de LAMBDA-EDGE.
Pour contourner ce problème-là, des algorithmes existent.
Vous allez devoir, pour chaque état, déterminer quels sont les états équivalents à celui-ci.
Pour commencer, vous allez développer dans la classe FSA une fonction closure qui pour un
état, ou un ensemble d’états retournera tous les états accessibles et ceci récursivement.
Note : le/les états initial/initiaux font parti du résultat.
Par exemple :
closure(S0) −→ S0 S1 S9
Ensuite, vous ferez une fonction membre qui permet de retrouver tous les états joignables
via un chemin donné à partir d’un ensemble d’états initiaux.
À partir de ce moment-là, vous avez toutes les briques permettant de convertir votre machine à état non déterministe (NFA) en machine à états déterministe. L’algorithme présenté
ici n’est peut-être pas le plus optimal, mais il est simple à mettre en place.
Son but est de regrouper certains états du NFA qui sont équivalents. Attention, ça ne veut
pas dire qu’il ne créera pas plus d’états au final.
Algorithm 1 The subset algorithm
df a_states ← 0 {It will hold all DFA states}
initial ← closure(S0)
processing ← initial {processing is a kind of queue}
while processing is not empty do
current ← pop(processing)
for each edge in alphabet do
states ← move(current, edge)
closure ← closure(states)
if closure not in df a_states then
Add closure to df a_states
Push closure to processing
end if
Add edge from current to closure
end for
end while
Certaines informations ont été omises volontairement.

II.4.1

Travail

LAMBDA-EDGE
Simple formalité, mais vous devez introduire la gestion des LAMBDA-EDGE dans votre
classe Edge. Ajoutez les accesseurs qu’il faut pour savoir si une instance de cette classe représente un LAMDA-EDGE.

16

Voyage en enfer

Conseil : Ne vous prenez pas trop la tête, un LAMDA-EDGE pourrait être une instance de
Edge ayant son chemin à -1. De plus, il pourrait y avoir une instance statique et constante appelée LAMBDA dans cette classe afin de pouvoir les créer/tester sans forcement devoir recourir
à des accesseurs : le code n’en sera que plus lisible.
Closure
Créez une fonction membre dans la classe FSA qui permet de récupérer les états joignables
via des LAMBDA-EDGEs. N’oubliez pas que les états initiaux font partie du résultat.
Move
Créez une fonction membre dans la classe FSA qui permet de récupérer une liste d’états
joignable via un chemin donné (c.-à-d. un Edge).
Subset
Implémentez l’algorithme dans une fonction membre de FSA. La fonction membre devra
renvoyer le nouveau FSA, cette fonction sera constante. Introduisez une notion de type de
FSA (NFA/DFA).
Cet algorithme peut devenir très lent s’il y a beaucoup d’Edge. 1 à 2 points bonus pour
une optimisation simple sans changer d’algorithme. 5 points pour une implémentation d’un
autre algorithme plus performant (si vous avez le temps après avoir fini tous les chapitres et
les étapes, il y a encore beaucoup de travail).

Memoisation (il n’y a pas de faute de frappe).

Rendu
Dans le dossier : <racine du dépôt>/purgatory/etape4
Binaire : test_nsa_to_dfa
Toujours pareil, faites un fichier de test le plus exhaustif possible.

II.5

Étape 5

Bien que notre machine à état commence à ressembler à quelque chose d’utilisable, il est
encore très douloureux de l’utiliser : il faut beaucoup de code pour générer un FSA.
Nous allons tenter de réduire le code requis, mais pour cela nous allons avoir besoin de
deux "algorithmes" : Un algorithme de concaténation et un algorithme d’union de FSA.
Afin d’avoir un peu de visibilité sur ce que vous faites, vous allez aussi faire en sorte de
générer une image de vos FSA.

17

Voyage en enfer

II.5.1

Travail

Dans un premier temps, ajouter une ou plusieurs surcharges d’opérateur afin de pouvoir
afficher un FSA au format dot 2 . Les états finaux devront être remplis en rouge, exactement
comme les exemples.
Ensuite, créez une fonction membre statique pour concaténer ou unir deux FSA.
L’union de deux FSA peut être vue de deux manières différentes. Votre fonction doit proposer les deux et sera activée via un paramètre optionnel.

Figure II.5 – FSA initiaux
2. http://www.graphviz.org/

18

Voyage en enfer

Figure II.6 – Union de type 1

19

Voyage en enfer

Figure II.7 – Union de type 2

20

Voyage en enfer

Figure II.8 – Concaténation
rendu
Dans le dossier : <racine du dépôt>/purgatory/etape5
Binaire : test_opdot_fsa

21

Voyage en enfer

Et beaucoup d’images !

II.6

Étape 6

Maintenant que vous pouvez manipuler vos FSA les doigts dans le nez, nous allons rendre
ça utilisable !
Vous allez faire une sorte d’évaluateur d’expression. L’expression sera une chaine de caractère.
Pour générer une machine à état à partir de la chaine "mechant", nous pouvons voir ça
comme une simple opération : m + e + c + h + a + n + t. Mais il ne faut surtout pas voir ça
comme étant une seule machine à état, mais comme la concaténation de 7 machines à états
différentes.
Allons plus loin : "(mechant)|(criminel)". Comment voir ça ? Le ’|’ représente l’union
de deux FSA, chacun de ces FSA est respectivement la concaténation de 7 et 8 FSA.

II.6.1

Travail

Vous devez écrire une classe ExpressionParser. Elle prendra en paramètre la chaine de
caractère qui représente le motif à chercher dans le flux.
Modifiez votre classe Matcher pour qu’elle puisse prendre un motif en paramètre. C’està-dire qu’elle doit être capable d’utiliser votre nouvelle classe pour générer le bon DFA.
Je vous conseille de faire une fonction getNextFSA qui renverra le prochain FSA qui se
cache dans la chaine de motif. Faites un parseur en descente récursive : Par exemple lorsque
vous allez appeler getNextFSA pour la première fois, il va lire la parenthèse ouvrante, et
s’appeler récursivement pour consommer tous les caractères à l’intérieur. L’appelant devra
faire attention à bien chercher la parenthèse fermante pour ne pas s’appeler une fois de trop.
Pour gérer le cas du "ou", dites vous que c’est seulement un opérateur. Et qu’un opérateur
se trouve après le FSA lu.
L’union à effectuer est de type 2.
Rendu
Dans le dossier : <racine du dépôt>/purgatory/etape6
Binaire : test_improved_pattern

II.7

Étape 7

Vous êtes bientôt arrivé à la fin de votre quête : désengorger le purgatoire ! Vous êtes
maintenant capable de détecter les méchants, mais encore faudrait-il pouvoir le notifier à qui
de droit.
Pour cela, vous allez réutiliser ce que vous aviez fait dans la barque de Charon. . .
Travail
Ajoutez dans la classe d’état une instance de Function permettant de stocker n’importe
quel foncteur prenant une chaine de caractère constante.
Lorsque le Matcher s’arrête sur un état final, et que cet état final contient un foncteur, il
devra alors l’appeler en passant le token trouvé.
22

Voyage en enfer

Prenez garde aux fonctions d’union/concaténation. Modifiez ce qui vous semble nécessaire pour qu’on puisse tirer pleinement parti de cet ajout.
Rendu
Dans le dossier : <racine du dépôt>/purgatory/etape7
Binaire : test_notifier

II.8

Étape 8

Zut ! Vous vous apercevez maintenant que vous n’êtes plus capable de trouver les grands
méchants... Vous vous devez d’implémenter des motifs de recherche bien plus puissants !
Pour cela, vous allez rajouter des opérateurs !

II.8.1

Travail

Rajoutez à votre parseur la gestion des opérateurs : ’? + ?’

Remarques :
• ’?’ : 0 ou plusieurs ;
• ’+’ : 1 au minimum ;
• ’ ?’ : 0 ou 1 ;
• ’a+’ : aa?.
Avec ces opérateurs les SEULS changements à faire sont au niveau du parseur de l’expression (on peut le dire maintenant) rationnelle. En effet, vous avez juste des LAMBDA-EDGEs
à rajouter par ci par là.
Pour l’opérateur ’+’, petite subtilité, vous devez implémenter un opérateur de copie dans
le FSA : vous devez copier le dernier FSA lu, le copier, le réinjecter, appliquer l’opérateur ’*’
et concaténer le résultat.
Rendu
Dans le dossier : <racine du dépôt>/purgatory/etape8
Binaire : test_regexp
Bonus : 1 points pour ceux qui implémentent les opérateurs de plage de caractères : "[azA-Z0-9]+_[a-zA-Z0-9]+". 2 points pour ceux qui implémentent l’opérateur ’.’ (correspond à
n’importe quel caractère, je vous conseille de l’implémenter de manière non gourmande).
MAIS attention : si nos tests ne passent pas sur votre moteur d’expression régulière vous
aurez 0 (à l’étape), implémenter les deux opérateurs demande des modifications à pas mal
d’endroits afin de marcher. De plus, il est très facile d’oublier LE cas où ça ne marche pas.
Autre chose, si vous implémentez les plages de caractères, l’exemple donné avec un code C++
propre de base, mais sans l’algorithme du subset amélioré, cela peut prendre des minutes sur
de grosses expressions. Si vous arrivez en soutenances et qu’il nous faut attendre 30 minutes
23

Voyage en enfer

avant de voir si ça marche, nous ne mettrons pas les points bonus (et si on n’est pas de bonne
humeur, on risque de ne même pas attendre).

II.9

Étape 9

Dernière étape avant de pouvoir passer le Jugement.
Vous êtes capable de rechercher des motifs complexes dans une essence d’âme. Mais, vous
ne pouvez qu’en faire un à la fois...
Votre dernier travail sera de créer une sorte de Lexer enfantin.
Travail
Arrangez-vous pour que ça, ça compile :
Lexer l;
l
.addLexer("-?(0|1|2|3|4|5|6|7|8|9)+))", &integer)
.addLexer("(\"me+cha+nt\")|(criminels*)", &criminel);
l.consume(some_string);

Indice :

Il n’y à qu’une seule fonction membre (requise) non utilisée. . .

Rendu
Dans le dossier : <racine du dépôt>/purgatory/etape9
Binaire : test_lexer

II.10

Le Jugement

Les Archanges ont senti ce que vous faisiez et vous demandent de leur montrer comment ça marche ! Ils sont contents, et vous sentez que vous pourrez marcher sur les Champs
Élysée 3 , mais soudain tout devint froid et une mélopée horrible se fait entendre ...

3. Ceux là : http://fr.wikipedia.org/wiki/Champs_%C3%89lys%C3%A9es_(mythologie)

24

Chapitre III
L’appel du Diable
Ph’nglui mglw’nafh Cthulhu R’lhel wgah-nagl fhtagn - Dans sa demeure de
R’lyeh la morte, Cthulhu rêve et attend.
Le Necronomicon - Lovecraft
N’est pas mort ce qui à jamais dort, et au long des ères peut mourir même la
mort.
Le Necronomicon - Lovecraft
Le Diable. Le Diable n’est pas satisfait de votre outil ! Comment s’en contenter sachant
que vous ne pouvez passer que des pointeurs sur fonction, ou encore des objets que l’on peut
appeler ? Vous en conviendrez, il a de quoi râler... On devrait pouvoir passer des objets plus
complexes. Notamment des instances et des pointeurs sur méthodes... Et soyons fou, pourquoi
pas des paramètres fictifs (placeholder) ?
Vous allez descendre encore plus loin dans cet enfer, mais avant vous allez avoir besoin de
savoir ce que vous faites. Vous allez avoir besoin d’ouvrir votre esprit à ce que des cerveaux
torturés ont pu inventer...

III.1

Un mot sur les templates

Les templates. Vous entendez ce mot, parfois le comprenez, souvent vous les utilisez, mais
peu de gens savent jusqu’ou on peut aller.
Les templates sont des sortes de macro un peu bâtardes qui permettent de générer du code
avec une vérification du typage un peu plus poussée. Cela permet de faire notamment de la
spécialisation de code pour un type de donnée. C’est littéralement un langage au dessus du
C++. C’est aussi sa plus grosse faiblesse : l’écriture de template est quelque chose de fastidieux
et difficilement debuggable. Le LISP est aussi le premier langage à avoir donné la possibilité
de générer du code via l’interpréteur, cependant, la grande force du LISP c’est qu’une macro,
lorsqu’elle est évaluée, génère son code en LISP.C’est-à-dire que vous générez du LISP en
LISP, ce qui en plus d’être plus cohérent est bien plus lisible.

III.1.1

Le sizeof

L’opérateur sizeof est un peu particulier. Vous savez qu’il renvoie la taille d’un type en
octet, mais en réalité, ce n’est pas la taille d’un type, mais d’une expression ! De plus, ce qu’il
faut savoir c’est que cette expression n’est pas compilée (elle est constante et statique) !
25

Voyage en enfer

Où est-ce que cela nous mène ?
Les sizeof sont évalués statiquement. C’est-à-dire qu’au moment de l’écriture de template vous pouvez tester la valeur de retour d’un sizeof.

III.1.2

SFINAE

Lorsque le compilateur C++ considère toutes les surcharges possibles pour un appel de
fonction donné, il va choisir celle où il y a le plus d’affinité avec. Par exemple, faites une classe
qui a un constructeur qui prend un booléen et un autre avec une référence sur une chaine de
caractère constante, instanciez cette classe en passant un pointeur sur un tableau de caractère
constant et vous verrez que c’est le constructeur prenant le booléen qui sera appelé.
SFINAE ou «Substitution Failure Is Not An Error» est la situation rencontrée par le compilateur C++ quand il instancie un template C++ et qu’il se retrouve avec une substitution
invalide : ce n’est pas une erreur irrécupérable s’il y a d’autres surcharges à tester.
À partir de ce constat, il est possible de tester la validité de certaines expressions en les
mettant dans la définition du type template et d’en mesurer le retour, et de tomber dans un
cas qui marche toujours afin d’éviter les erreurs de compilation.

III.1.3

Exemple

#include <iostream>
template <typename T>
struct has_typedef_type
{
// yes and no are guaranteed to have different sizes,
// specifically sizeof(yes) == 1 and sizeof(no) == 2
typedef char yes[1];
typedef char no[2];
template <typename C>
static yes& test(typename C::type*);
template <typename>
static no& test(...);
// if the sizeof the result of calling test<T>(0) is equal to the sizeof(
yes),
// the first overload worked and T has a nested type named type.
static const bool value = sizeof(test<T>(0)) == sizeof(yes);
};
struct foo
{
typedef float type;
};
int main()
{
std::cout << std::boolalpha;
std::cout << has_typedef_type<int>::value << std::endl;
std::cout << has_typedef_type<foo>::value << std::endl;
}

Listing III.1 – Code tiré de wikipedia
26

Voyage en enfer

L’ellipse est la surcharge ayant la moins d’affinité.

III.1.4

Travail

Afin de vous échauffer, vous devez faire un inspecteur d’objet : Créez une fonction qui
prend n’importe quoi en paramètre et qui affichera le nom du type de l’objet (via un typeid)
si jamais l’opérateur ’<<’ n’est pas implémenté. Et si jamais la surcharge de l’opérateur ’<<’
existe, vous l’appellerez sur une l’instance std::cout.
Attention, cette partie a surtout pour but de vous entrainer et que vous compreniez comment fonctionnent les templates. Cela n’a que peu de rapport avec la suite.
Exemple :

#include "Inspector.hpp"
struct test{};
struct lol{};
std::ostream& operator<<(std::ostream& out, const lol&)
{
out << "Lol instance given" ;
return out;
}
int main(int, char**)
{
test t;
lol l;
std::string str = "salut";
inspect(t);
inspect(str);
inspect(l);
return 0;
}

Sortie :
$> ./test_inspector
4test
salut
Lol instance given
Cette étape, n’étant pas le but final de ce chapitre sera très guidé et sur peu de points. Elle
a plus une portée pédagogique.
Le but de cette étape est tout simple : vous devez écrire une classe contenant un champ
indiquant si une surcharge est présente ou pas.
Donc, lançons-nous :
• Créez une classe (ou structure) IsPrintable prenant deux types templates ;
• Créez deux types à l’intérieure de la classe de taille différente ayant pour nom : Yes et
No ;
27

Voyage en enfer
• Créez une constante/enum/ce que vous voulez appelé ISPRINTABLE ;
ISPRINTABLE sera à 1 si jamais la surcharge existe, à 0 sinon. Le premier type template
représente la classe du flux (std::ostream par exemple) et le second le type de l’objet testé.
Comme dit précédemment, le sizeof permet de récupérer la taille du type de retour d’une
méthode (entre autres). J’ai aussi dit qu’une méthode prenant des ellipses en paramètre est la
surcharge ayant là moins d’affinité.
Partons d’une méthode nommée isPrintable. Cette méthode sera surchargée deux fois et
vous pouvez supputer que l’ellipse sera l’une d’elles (et vous pouvez deviner son type de
retour)... Créez-la !
À partir de maintenant, il nous faut violer un peu la politique d’affinité du compilateur.
L’expression dont on doit tester la validité est :
Object o;
Stream s;
s << o;

Essayez de factoriser cette expression afin de l’avoir sur une ligne :
• Créez une référence nulle sur un Stream ;
• Créez une référence nulle sur un Object ;
• Combinez les deux !
Maintenant que vous avez cette (horrible) ligne, il faut trouver ou la mettre.
Nous savons que pour pouvoir compter sur le SFINAE, nous devons avoir une substitution
de template donc notre ligne doit aller dans une définition de méthode template.
La surcharge devra donc prendre un type template.
Afin d’aider le compilateur, cette surcharge prendra un pointeur sur le type template.
Maintenant nous devons trouver ou placer notre expression. Naïvement (moi le premier),
ce qui paraitrait naturel est de mettre cette expression en temps que valeur par défaut à un
second argument. Par exemple, on aurait pu imaginer un :
template <typename U>
Yes isPrintable(U*, size_t s = sizeof(expression_foireuse));

Cela ne marchera pas. Le SFINAE ne marche que sur la définition d’un type et non pas
sur une expression, c’est-à-dire que expression_foireuse (et le sizeof) DOIT faire partie
de la définition d’un type !
Donc, créez une classe prenant en template un size_t. Faites en sorte que la méthode
prenne un pointeur nul sur cette instance (avec comme valeur par défaut 0) et mettez dans la
définition du type l’expression.
Maintenant, il ne vous reste plus qu’à initialiser ISPRINTABLE de manière à ce qu’il soit
égal à 1 si la surcharge est présente.
À ce niveau-là, vous êtes maintenant capable de déterminer si jamais la surcharge est
présente ou pas. Il ne vous reste plus qu’à implémenter la fonction inspect.
28

Voyage en enfer

Pour cela on va avoir besoin d’un peu plus de métaprogrammation afin de faire les choses
correctement.
Vous allez réimplémenter et réutiliser un enable_if. Il est très très simple à implémenter,
c’est une sorte de meta-if déguisé. Il peut se templater avec deux types différents : le premier
étant un booléen, le second un type quelconque égale à un pointeur sur void par défaut.
Dans l’implémentation par défaut il y aura une définition de type du second paramètre
nommé type. Dans la spécialisation (où le booléen est faux), la définition sera vide.
Concrètement à quoi ça sert ? Bien, nous allons encore utiliser le SFINAE : le enable_if
va nous permettre de définir un type si une condition est vérifiée.
Vous allez créer une structure qui se template avec le type du flux, vous allez implémenter
deux méthodes statiques se templatant sur le type de l’objet à afficher et prenant le flux, l’objet
et en troisième paramètre optionnel le type donne par un enable_if sur l’évaluation de la
présence de la surcharge. Dans l’une des deux méthodes, c’est la présence de la surcharge
qui sera testée et dans ce cas là, dans le corps de la méthode la surcharge sera appelée, dans
l’autre il faut tester l’absence et afficher le nom du type via le typeid.
Dans la fonction inspect, il faudra appeler la méthode définie dans la structure.
Le rendu
Tout le code template sera à rendre dans le fichier : <racine du dépôt>/devil/Inspector.hpp
Des tests devront être présentés en soutenance et devront être compilés via CMake.
Indice
En vrac :
typedef struct {int tab;} Yes;
typedef struct {int tab[2];} No;
static No isPrintable(...); // one of the overloading
static const bool ISPRINTABLE = (sizeof(isPrintable((Object*)0)) == sizeof(Yes
));
template <bool Cond, typename T = void*>
struct enable_if
{
typedef T type;
};
template <typename T>
struct enable_if<0, T>
{};
// Example of how inspect could be implemented
template <typename Object>
void inspect(const Object& o)
{
Printer<std::ostream>::print(std::cout, o);
}

29

Voyage en enfer

III.2

L’antichambre

Vous êtes en rush et si vous en êtes arrivés là en ayant réussi toutes les étapes d’avant,
vous méritez déjà des félicitations. Mais ce n’est pas le but de ce rush, vous allez être poussés
à bout, vraiment à bout.
Le C++ est un sujet inépuisable.
Pour la suite, vous allez devoir me faire confiance encore plus qu’auparavant et me suivre
à la lettre. Je ne prétends pas vous faire coder le ‘bind’ du siècle. Par contre, il marche.
Dans ce chapitre, trois évaluations seront faites, un peu à la manière des points de sauvegarde de progression dans un jeu :
• Vous aurez des points quand vous aurez fait un bind qui fonctionne sur des pointeurs
sur fonction et objets ‘Callable’ ;
• Vous en aurez quand vous aurez rajouté les pointeurs sur méthode ;
• Et enfin quand vous aurez les paramètres fictifs.
Je vous proposerais mon aide tout au long de ces étapes, vous pourrez la suivre ou simplement l’ignorer, c’est à vous de choisir. Je vous guiderais tantôt de manière précise tantôt
de manière vague. Autre conseil : codez sous Windows ET Linux en même temps, les templates sont un sujet sensible et vous pouvez vous retrouver avec des erreurs sur l’un des deux
systèmes.
Ha, aussi, ne faites pas l’erreur de ne pas nous croire capables de reconnaitre quand un
code n’est pas le vôtre SURTOUT dans du code de ce genre.
Nous allons maintenant attaquer la création d’un ‘bind like’. En soi, ce n’est pas très
compliqué, le code reste relativement simple. Ce qui est compliqué est de saisir toutes les
parties devant s’imbriquer.
Dans un premier temps, nous allons créer un ‘bind’ fonctionnant pour les pointeurs
sur fonction et les objets appelables (redéfinissant l’opérateur parenthèse). À partir de ce
moment-là, nous pourrons implémenter la création d’un ‘bind’ sur les pointeurs de méthode.
Pour finir, nous ajouterons les paramètres fictifs (placeholder).
Le problème du ‘bind’ est que tout doit se faire en même temps. Vous allez écrire beaucoup
de code dans un premier temps, mais le tester sera compliqué tant que tout ne sera pas fait.
Vous devrez créer des classes de traits afin de dégager quelques informations sur les types
que vous manipulez. Vous devrez créer et manipuler des listes de types en étant capable de
déterminer si le type réel n’a pas été enveloppé dans une autre classe (fonction ‘ref’ dans le
bind de boost par exemple).

III.3

Étape 1 ou les effluves du démon

Le tout sera à rendre dans le repertoire : <racine du dépôt>/hell/devil/bind
Pour commencer, vous allez créer un ensemble de classe qui va permettre de stocker les
paramètres. C’est du code trivial.
Vous allez créer une classe Storage0 vide, une classe Storage1 héritant de Storage0
ajoutant un paramètre template, une classe Storage2 héritant de Storage1 ajoutant un paramètre, etc. jusqu’a Storage6.
Évidemment un constructeur devra être présent prenant le bon nombre d’arguments.

30

Voyage en enfer

template <typename T1, typename T2, typename T3, typename T4, typename T5,
typename T6>
struct Storage6 : public Storage5<T1, T2, T3, T4, T5>
{
Storage6(T1& t1, T2& t2, T3& t3, T4& t4, T5& t5, T6& t6) : Storage5<T1, T2,
T3, T4, T5>(t1, t2, t3, t4, t5), _t6(t6)
{
}
T6 _t6;
};

Listing III.2 – Exemple avec Storage6

III.4

Étape 2 ou la désorientation maléfique

Dans un autre fichier, vous allez créer un ensemble de classe de traits permettant de définir
le type que l’on va stocker. C’est-à-dire qu’à la création d’une instance de notre bind, nous
allons contrôler ce que nous recevons et ce que nous allons stocker.
Créez dans un autre fichier (que le précédent) :
• Creez une classe Value pouvant prendre un type template, qui stocke un élément du
type donné, qui a un accesseur appelé get qui renvoie une référence vers ce type là,
une surcharge de ce dernier qui renvoie une référence constante.
• Creez une classe de trait appelé GetType qui prend un type en template et qui défini
un type Type représentant un type Value paramètre avec le type donné à GetType.
• En C++, on y pense rarement, mais on peut passer des types en paramètre. C’est utile
quand certain compilateur ne supporte pas certain type d’expression. C’est tellement
simple qu’on y pense jamais. Créez une structure appelé TypeTraits prenant simplement un type template, elle restera vide. Nous verrons plus tard comment l’utiliser.

III.5

Étape 3 ou la métamorphose

Cette étape est cruciale pour la suite. C’est aussi la plus complexe, car vous allez devoir
commencer à saisir le bind dans sa généralité. Pour le moment vous pouvez vous douter
comment seront stockées les données, mais vous ne savez pas encore où... Je vais tenter de
vous éclairer sur la suite.
Un bind est concrètement un foncteur avec une couche permettant de sauvegarder les
paramètres avec lequel il doit être appelé.
Une fonction (fonction au sens large, cela inclut tous les types de foncteur) se caractérise
par deux éléments : son type de retour et ses arguments. À cela nous devons ajouter le type
de cette fonction : Pointeurs sur fonction, objet appelable, etc.
Ces trois informations sont les informations nécessaires et suffisantes pour créer notre
super foncteur.

31

Voyage en enfer

Vous allez donc créer une classe «Caller» se paramétrant avec trois types template :
• Le type de retour (ReturnType) ;
• Le type de l’objet appelable (Callable) ;
• Le type de la liste de paramètre (List, je reviens dessus après).
Créez un constructeur prenant un Callable et une List en paramètre et qui le stocke
dans l’instance.
Dans la classe Caller, c’est à cet endroit que nous trouverons les surcharges de l’opérateur parenthèse, mais le code vraiment utile sera dans nos classes de liste.
Les classes de liste sont simples à programmer, mais un peu moins a comprendre. À
chaque classe StorageX créée, il faut une classe TypeListX correspondante. La classe TypeListX
doit hériter de la classe StorageX correspondante (en privé).
Dans notre bind, deux listes de types seront créées : une lors de la création de notre
instance de Caller et une lors de l’appel de l’opérateur parenthèse.
La création de la première liste doit être "surveillée" dans le sens ou les types données
auront une certaine importance. Effectivement, si vous voulez par exemple stocker des références, ou indiquer que des arguments ne seront donnés que lors de l’appel vous devez être
capable de l’indiquer.
De ce fait c’est la classe TypeList qui fera réellement l’appel de l’opérateur parenthèse de
Callable. À ce moment la une autre TypeList sera donnée et elle contiendra les arguments
donnés lors de l’appel.
Dans la suite vous allez avoir un ensemble de schémas sur comment les classes vont s’imbriquer entre elles. Certaines informations sont encore absentes.
Dans la figure III.1, représentons une simple fonction renvoyant un entier et prenant trois
arguments de type A, B et C. Dans la figure III.2, vous pouvez voir ce que comporte une
instance de la classe Caller avec les bons types donnés en template (dans l’optique de faire
un bind avec la fonction f). À partir de là, il est facile de déterminer quels seront les types
manipulés par Caller (III.3). Si des paramètres avaient été donnés lors de la création de la
classe Caller, ils seraient stockés dans notre TypeList3.
Maintenant lorsque nous appelons notre surcharge d’opérateur de Caller (figure III.4),
une nouvelle liste de type est créée et nous appelons la surcharge de l’opérateur parenthèse de
notre liste stocké dans notre instance de Caller. En paramètre nous donnons un TypeTraits
qui contient le type de retour de notre Callable (à vous de deviner comment l’utiliser), le
Callable et enfin la liste nouvellement construite. Dans le schéma, nous pouvons voir que
x, y et z ont été passé en paramètre. Ils sont stockés tels quels dans la TypeList car il n’y a
aucun besoin de savoir de quel type ils sont.
Et enfin (figure III.5), l’appel effectif a Callable.Ce que vous voyez avec les listes est tout
simplement le petit ‘truc’ du bind. Dans la liste courante _t1, _t2 et _t3 contiendront les
éléments présents lors de la création de l’instance de Caller. Par exemple dans le cas d’un
appel du genre :
bind(&f, a, b, c);

32

Voyage en enfer
• _t1 = Value<A> et contient a ;
• _t2 = Value<B> et contient b ;
• _t3 = Value<C> et contient c.
Si vous analysez le code, vous remarquerez qu’on donne en paramètre à l2 ces instances.
En effet, c’est l2 qui doit déterminer comment renvoyer la valeur voulue.
À partir de toutes ces informations, vous devriez être capable de faire un bind gérant au
moins les fonctions et les callables. Ce n’est pas évident, mais vous avez toutes les pièces
d’information ‘vitale’ dans vos mains.

Figure III.1 – Prenons une fonction f quelconque.

Figure III.2 – Créons une instance de Caller.

33

Voyage en enfer

Figure III.3 – Analyse du contenu de TypeList3.

34

Voyage en enfer

Figure III.4 – Appelons l’opérateur parenthèse de Caller.

Figure III.5 – La surcharge de l’opérateur dans TypeList3.
Voici quelques morcaux de codes en vrac :
35

Voyage en enfer

template <typename P1, typename P2, typename P3>
struct TypeList3 : private Storage3<P1, P2, P3>
{
typedef Storage3<P1, P2, P3> BaseClass;
TypeList3(P1 p1, P2 p2, P3 p3) : BaseClass(p1, p2, p3)
{
}
template <typename T>
T& operator[](Value<T>& value)
{
// ....
}
template <typename ReturnType, typename Caller, typename List>
ReturnType operator()(TypeTraits<ReturnType>, Caller& caller, List& list)
{
return caller(list[BaseClass::_t1],
list[BaseClass::_t2],
list[BaseClass::_t3]
);
}
};

Listing III.3 – Partie du code de la classe TypeList3

template < typename ReturnType, typename X1, typename X2, typename X3,
typename Param1, typename Param2, typename Param3 >
Caller < ReturnType, ReturnType(*)(X1, X2, X3), typename Traits3 < Param1,
Param2, Param3 >::Type >
bind(ReturnType(*f)(X1, X2, X3), Param1 p1, Param2 p2, Param3 p3)
{
typedef typename GetType < Param1 >::Type P1;
typedef typename GetType < Param2 >::Type P2;
typedef typename GetType < Param3 >::Type P3;
typedef TypeList3 < P1, P2, P3 > ListType;
ListType l(p1, p2, p3);
return Caller < ReturnType, ReturnType(*)(X1, X2, X3), ListType > (f, l);
}

Listing III.4 – Fonction bind appelée dans le cas d’une fonction à trois paramètres

template < typename P1, typename P2, typename P3>
struct Traits3
{
typedef typename GetType < P1 >::Type Type_Parameter_1;
typedef typename GetType < P2 >::Type Type_Parameter_2;
typedef typename GetType < P3 >::Type Type_Parameter_3;

36

Voyage en enfer

typedef TypeList3 < Type_Parameter_1, Type_Parameter_2, Type_Parameter_3 >
Type;
};

Listing III.5 – Classe de traits aidant à la détermination du type de la liste

III.6

Étape 4 ou comment devenir fou

Si vous avez passé l’étape précédente, vous êtes presque au bout de vos peines ! Le reste
est presque trivial.
Rajoutez la gestion des pointeurs sur méthodes. Le code existant ne doit pas bouger, ce
qui change par rapport à un pointeur sur fonction c’est la manière d’exprimer le pointeur sur
méthode ET le fait qu’elle puisse être constante !
Indice : le pointeur sur méthode doit aussi être traite comme un callable à part entière.
On vous a appris qu’en réalité le pointeur de l’instance est donné en premier paramètre à une
méthode. Vous pouvez vous en inspirer pour implémenter la classe contenant un pointeur
sur méthode.

III.7

Étape 5 ou la torture mentale

Rajoutez un moyen de pouvoir passer des classes non copiables en argument.
Indice : c’est ce que fait ref dans boost.

III.8

Étape 6 ou le suplice utilme

Rajoutez de la même manière les paramètres fictifs. Pensez au TypeTraits.

III.9

Étape 7 ou la paix

Ce n’est pas vraiment une étape. Faites un fichier de test exhaustif, et allez vous reposer :)

37

Chapitre IV
Consignes générales
• PAS DE C++11.
• Vous DEVEZ utiliser CMake pour compiler votre code. Les rendus n’utilisant pas CMake
recevront la note de 0, SANS EXCEPTIONS.
• Vous etes autorisés à utiliser boost::shared_ptr et BOOST_FOREACH.
• Votre code devra être portable sous un Windows de votre choix (msvc) et un Linux de
votre choix (gcc).
• Toute valeur passée par copie plutôt que par référence ou par pointeur doit être justifiée, sinon vous perdrez des points.
• Toute valeur non const passée en parametre doit être justifiée, sinon vous perdrez des
points.
• Toute fonction membre ou méthode ne modifiant pas l’instance courante mais n’étant
pas const doit être justifiée, sinon vous perdrez des points.
• Il n’existe pas de norme en C++. Cependant, tout code que nous jugerons illisible ou
trop sale pourra être sanctionné arbitrairement. Soyez sérieux !
• Gardez un œil sur ce sujet régulièrement car il est susceptible d’être modifié.
• Nous attachons une grande importance à la qualité de nos sujets, donc si vous trouvez
des fautes de frappe, d’orthographe ou des incohérences, merci de nous contacter à
l’adresse koala@epitech.eu pour que nous puissions y remédier dans la journée.
• Vous pouvez joindre les auteurs par mail, voir l’en-tête.
• Le forum de l’intranet contiendra des informations et des réponses à vos questions.
Assurez-vous d’abord que la réponse à votre question ne figure pas déjà sur le forum
avant de contacter les auteurs.

38

Chapitre V
Consignes de rendu
Vous DEVEZ rendre votre projet sur le Git fourni par Epitech. Le nom du depot est
rushhell.
Corollaire à loi de Murphy : "Si tu rends ton travail dans la dernière heure, quelque chose
va mal se passer".
Seul le code présent sur votre dépôt sera évalué lors de la soutenance.
Bon courage !

39



Documents similaires


hell
tuto calibre
consignes exposes
tuto maison clacla 1
004
glinkomodedemploigerantdesocietedecourses