Bonjour a tous,
J'ai programmé une bibliothèque c++ dans le but d'alléger la mise en oeuvre d'un Multiple Dispatch en utilisant un pattern Visitor simple (pas de RTTI)

Le principe est le suivant :
Chaque classe de la hiérarchie implémente le mécanisme accept(AVisitor& v), ma bibliothèque s'en sert pour permettre le multiple dispatch.

Vous trouverez ci-joint la bibliothèque (et un header de compatibilité c++1y), ainsi que deux fichiers .cpp d'exemple.
Par commodité, vous trouverez également ci dessous le fichier source de la bibliothèque ainsi qu'un des deux fichiers d'exemple.

Qu'en pensez-vous ? Toutes remarques et tous conseils sont les bienvenus !
Merci d'avance

EDIT : Je met les codes à jour au fur et à mesure des corrections. Dernière mise a jour : 17.09.14

Fichier joint : Multiple Dispatch Release v1.1.zip
Un copier-coller de code multiple_dispatch.h :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
 
#ifndef Multiple_Dispatch_Via_Visitor_Wrapper_h
#define Multiple_Dispatch_Via_Visitor_Wrapper_h
/************************************************************************
 Project : Multiple Dispatch Wrappers
 File    : multiple_dispatch.h ,
           Single header, sans fonctionnalité avancée
 
 Author  : HARBULOT Julien
 
 Copyright (C) 2014 HARBULOT Julien Edmond René
 
 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.
 
 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.
 
 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 email : info [at] julienh.fr
 
 ************************************************************************
 * Système de multiple dispatch à N arguments (pour tout N > 0)
 *
 * Utilisation :
 *  Chaque classe dont on voudra reconnaître le type implémente le
 *  pattern visitor [aka : elle possède une methode accept(...) ]
 *  Les wrappers prennent un nombre quelconque d'arguments et une action.
 *  Ils s'occupent de retrouver le type de tous les arguments via leurs
 *  méthodes accept(...), puis apellent l'action demandée.
 *
 * Version : 4 - std::tuple
 
 ************************************************************************/
 
 
#include <tuple>
 
//========================================================================
//================================ LIBRARY ===============================
//========================================================================
namespace dispatch{
namespace multiple_dispatch_impl{
 
    //====================================================================
    //====================================================================
    //
    // === Family ===
    //
 
    // A partir du type BaseClass, un dispatcher doit retrouver le bon type dérivé.
    // Pour cela, il doit connaitre la liste des types dérivés (DerivedList)
    // Et il doit pouvoir visiter la hiérarchie en héritant de VisitorBase
    // Ces données constituent une famille :
 
    template <class VisitorBase, class BaseClass, class... DerivedList>
    struct Family{
        template <class... Ts> struct L;
 
        using Visitor = VisitorBase;
        using Base = BaseClass;
        using Deriveds = L<DerivedList...>;
        //using ToList = L<VisitorBase, BaseClass, DerivedList...>;
    };
 
    //====================================================================
    //====================================================================
    //
    // === Multiple Dispatch class ===
    //
 
    template
    <
    class Action, class Data_t,
    size_t nFamilyToProcess, size_t curFamilyIndex, // MaxN et CurN
    class... Families
    >
    struct MultiDisp;
 
    //
    // On écrit : methodeGenerique(BaseClass1* d1, BaseClass2* d2, BaseClass3* d3, ...)
    // Et le MultiDisp appelle : ActionSpecifique(Derivee1A* d1a, Derivee2A*, d2a, Derivee3B*, d3b)
    //
    // Pour ce faire, le MultiDisp doit retrouver le type dérivé de ses nFamilyToProcess arguments.
    // Or, pour retrouver un type dérivé au sein d'une hiérarchie (Family), le MultiDisp
    //   implémente les fonctions visit() d'un curVisitor dont il hérite, action pour laquelle il doit connaitre
    //   toutes les classes dérivées possibles (DerivedList) de la hiérarchie.
 
    // Principe de résolution :
    // On a des pointeurs vers les classes mères d'une part (dans Data_t).
    // On a une liste de familles d'autre part (dans le parameter_pack).
    //
    // Le type des pointeurs ne change qu'au dernier moment (Data_t ne change pas).
    // Ce qui change, c'est notre parameter_pack qui fonctionne comme une file :
    // On enlève une famille à traiter au début (Step 1) et on ajoute le type trouvé a la fin (Step 2).
    // Ensuite, il suffit de caster chaque pointeur vers le type trouvé qui lui correspond (Condition d'arrêt).
    //
    // Pour savoir où on en est dans la file, on utilise deux index : CurN (index courant) et MaxN (nb familles)
    // Lorsque CurN == MaxN, on a traité toute la file
    //    et le parameter_pack ne contient plus que les types trouvés.
 
    // Nomenclature :
    // Une famille est representée par un parameter_pack de la facon suivante :
    // Family<curVisitor , curBase , DerivedList...>
    // L'avantage d'une telle représentation est de pouvoir utiliser le filtrage de motif sur DerivedList :
    // Family<DerivedList...> = Family<DerivedHead, DerivedTail...>
 
    //Step 1 :
    // On itère sur les derivées de la famille F1 pour implémenter les fonctions visit(...)
    // A chaque étape, la liste <DerivedTail...> décroit.
 
    template
    <
    class A, class D_t,
    size_t MaxN, size_t CurN,
    class curVisitor, class curBase, class DerivedHead, class... DerivedTail,
    class... otherFamilies
    >
 
    struct MultiDisp<A, D_t, MaxN, CurN, Family<curVisitor, curBase, DerivedHead, DerivedTail...> , otherFamilies...>
    : public MultiDisp<A, D_t, MaxN, CurN, Family<curVisitor, curBase, DerivedTail...>, otherFamilies...> //on enleve DHead
    {
    protected:
        using Parent = MultiDisp<A, D_t, MaxN, CurN, Family<curVisitor, curBase, DerivedTail...>, otherFamilies...>;
 
    public:
        using Parent::Parent;
        using Parent::visit;
        void visit(DerivedHead*){
            // On arrive dans cette fonction après avoir déclanché la visite (Step 2).
            // A ce stade, on a trouvé le type du pointeur correspondant à la famille CurN.
            // Le type trouvé (DerivedHead) est alors stocké à la fin de la file.
            // La recherche se poursuit pour les familles suivantes.
            // (Rappel : cas d'arrêt quand MaxN = CurN, (cf Condition d'arret)
            MultiDisp<A, D_t, MaxN, CurN+1, otherFamilies..., DerivedHead> next{ Parent::data };
            next.action();
        }
    };
 
    //Step 2 :
    // Fin de l'itération sur la famille courante : plus de dérivées.
    // On va utiliser le pattern visitor pour trouver le bon type dérivé.
    // Pour ce faire, on hérite du bon VisitorBase (curVisitorBase) et on visite le
    //    bon pointeur (dont on connait l'index CurN).
    //
    // La procédure se poursuit quand on arrive dans la bonne fonction visit(DerivedHead*)
    //    (voir plus haut).
    template
    <
    class A, class Data_t,
    size_t MaxN, size_t CurN,
    class curVisitorBase, class curBase,
    class... otherFamilies
    >
 
    struct MultiDisp<A, Data_t, MaxN, CurN, Family<curVisitorBase, curBase> , otherFamilies...>
    : public curVisitorBase
    {
    protected:
        Data_t const& data;
    public:
        MultiDisp(Data_t const& data) : data(data) {}
        void action(){
            std::get<CurN>(data)->accept(this);
        }
    };
 
    //Condition d'arrêt :
    // Quand CurN == MaxN, toutes les familles ont été visitées.
    // Comme à chaque fois les types trouvés ont été placés à la fin de la file,
    //    le parameter_pack <Families...> est devenu <KnownTypes...>.
    // Il nous reste à appeler l'action sur tous les arguments, après
    //    les avoir castés vers le bon type.
    template
    <
    class Action, class Data_t,
    size_t nbTypes,
    class... KnownTypes
    >
    struct MultiDisp<Action, Data_t, nbTypes, nbTypes, KnownTypes...>{
    protected:
        Data_t const& data;
    public:
        MultiDisp(Data_t const& data) : data(data) {}
 
        void action(){
            actionHelper(std::make_index_sequence<nbTypes>{});
        }
    private:
        template <size_t... I>
        void actionHelper(std::index_sequence<I...>){
            Action action;
            action(dynamic_cast<KnownTypes*>(std::get<I>(data))...); //@TODO
        }
    };
 
 
    // ===================================
    // Interface / gestion des pointeurs :
    //
    // Les pointeurs sont gérés par un std::tuple<BaseClass*...>
    // Pour éviter la duplication, c'est cette classe qui garde les pointeurs en mémoire.
    // On s'occupe aussi d'initialiser les index.
 
    template <class Action, class... Families>
    struct Dispatcher{
    private:
        using Data_t = std::tuple<typename Families::Base* ...>;
        using DispatcherImpl = MultiDisp<Action, Data_t, sizeof...(Families), 0, Families...>;
 
        Data_t data;
    public:
        Dispatcher(Action* a, typename Families::Base* ... to_solve)
        : data{to_solve...} {
        }
        void operator()(){
            DispatcherImpl{data}.action();
        }
    };
 
} //namespace implementation
 
using multiple_dispatch_impl::Family;
using multiple_dispatch_impl::Dispatcher;
 
} //namespace public
 
#endif // defined Multiple_Dispatch_Via_Visitor_Wrapper_h */
Un copier-coller de : exemple_simple.cpp
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
 
 
//
// Exemple d'utilisation simple de la bibliothèque MultipleDistpach
// Auteur : Julien Harbulot
//
 
#include <iostream>
 
#include "cpp11compatibility.h" //delete if c++1y
#include "multiple_dispatch.h"
 
using namespace std;
 
//===========================================================
// Utilisation basique de la bibliothèque MultipleDispatch.
//
//
// Dans cet exemple, nous allons effectuer un double dispatch (Note : Il est tout aussi simple d'effectuer un triple dispatch, ou plus)
//
// On possède une collection d' Animal* (Chat*, Chien*)
// On veut qu'il puissent s'attaquer entre eux en ayant des
//   comportements différents selon le type de l'attaquant
//   et de l'attaqué.
//
// Pour cela, on définit les comportements voulus dans une
//   classe ActionAttaquer.
// Par exemple : ActionAttaquer::operator()(Chat*, Chat*){}
//
// Ensuite on crée une fonction (ou une méthode) qui prend
//  en argument deux Animal* puis qui appelle le dispatcher,
//  et enfin l'action sur les types retrouvés.
//
// Pour pouvoir retrouver les bons types, il faut indiquer
// à la bibliotheque sur quelle familles on veut travailler.
//
// Une famille est constituée :
// - d'une interface de Visiteur (ex : AnimalVisitor)
// - d'une classe Mère (Animal)
// - de types dérivés (Chat, Chien)
//
// Par exemple, pour créer la famille Animaux :
//
// using Animaux
// = dispatch::Family<AnimalVisitor, Animal, Chat, Chien>;
 
 
 
//===========================================================
// Dans cette partie, tout est classique :
//
// On crée une hierarchie classique (avec pattern Visitor)
//    Chat et Chien héritent de Animal
//    Les classes implémentent : void accept(AnimalVisitor&)
//
 
class Animal;
class Chat;
class Chien;
 
class AnimalVisitor{
public:
    virtual void visit(Chat* c) = 0;
    virtual void visit(Chien* c) = 0;
};
 
class Animal{
public:
    virtual void accept(AnimalVisitor* v) = 0;
    virtual ~Animal(){}
};
 
class Chat : public Animal{
public:
    virtual void accept(AnimalVisitor* v){ v->visit(this); }
};
 
class Chien : public Animal{
public:
    virtual void accept(AnimalVisitor* v){ v->visit(this); }
};
 
//===========================================================
// Ici on utilise la bibliothèque :
//
// On crée une action qui nécessite un multiple dispatch
// On l'enregistre auprès d'un multiple dispatcher
// Nombre de lignes pour utiliser la bibliotheque : 3 lignes.
 
class ActionAttaquer{
public:
    void operator()(Chat* lhs, Chat* rhs) { cout <<  "griffe - griffe" << endl;}
    void operator()(Chat* lhs, Chien* rhs){ cout <<  "griffe - mord  " << endl;}
    void operator()(Chien* lhs, Chat* rhs){ cout <<  "mord   - griffe" << endl;}
    void operator()(Chien* lhs, Chien* rhs){ cout << "mord   - mord  " << endl;}
 
    void operator()(Animal* a1, Animal* a2){
        // D'abord on précise sur qui l'on veut effectuer le dispatch
        using Animaux = dispatch::Family<AnimalVisitor, Animal, Chat, Chien>;
 
        // Puis on appelle le Dispatcher
        using Dispatcher = dispatch::Dispatcher<ActionAttaquer, Animaux, Animaux>;
        Dispatcher(this, a1, a2)();
    }
};
 
//===========================================================
// fonction main(), on crée des Animal* et on essaie le
// double dispatch.
 
int main()
{
    cout << "Debut." << endl;
 
    Chat chat;
    Chien chien;
 
    Animal* a1 = &chat;
    Animal* a2 = &chien;
 
    ActionAttaquer attaquer;
    attaquer(a1, a2);
    attaquer(a2, a2);
 
    cout << "Fin." << endl;
    return 0;
}
[EDIT] Voici comment effectuer un triple dispatch :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
 
void operator()(Animal* a1, Animal* a2, Animal* a3){
        // D'abord on précise sur qui l'on veut effectuer le dispatch
        using Animaux = dispatch::Family<AnimalVisitor, Animal, Chat, Chien>;
 
        // Puis on appelle le Dispatcher
        using Dispatcher = dispatch::Dispatcher<ActionAttaquer, Animaux, Animaux, Animaux>; //ajout ici
        // Remarque : On peut utiliser trois familles différentes si on veut.
 
        Dispatcher(this, a1, a2, a3)();
    }