D'un langage de programmation à l'autre, le support à ce niveau
d'abstraction varie, et là où plusieurs langages permettent de
la métaprogrammation surtout dynamique (avec réflexivité,
métaclasses et autres
mécanismes de ce genre), C++
offre des mécanismes de métaprogrammation axées vers la
performance à l'exécution, donc accentuant le travail statique,
fait à la compilation.
D'autres manoeuvres ont pour but d'enrichir les capacités d'extension
et de spécialisation d'un programme. Par exemple, C++
offre (sur demande, car ce n'est pas gratuit) le Run Time Type Inference,
ou RTTI,
sur lequel repose entre autres le transtypage par dynamic_cast,
et à partir duquel on peut obtenir un nom pour chaque type.
Ces noms ne sont pas garantis portables d'un compilateur à l'autre,
ou d'une version à l'autre d'un même compilateur, mais il
est possible de les utiliser comme mécanisme « par défaut »
de nommage et de spécialiser les noms qui ne nous conviennent pas.
Par exemple, on pourrait spécialiser traits_noms<T>
lorsque T est
std::string, qui est au fond std::basic_string<char>,
pour que le nom retourné soit alors "string"
tout simplement).
|
#include <typeinfo>
template <class T>
struct traits_noms
{
static constexpr const char* nom()
{ return typeid(T).name(); }
};
|
Une manière simple et directe de dire si deux types sont en fait
un seul et même type est de combiner les outils déjà
présentés ici en une fonction projetant le message approprié
sur un flux donné.
|
#include <ostream>
using namespace std;
template <class T, class U>
void message_de_comparaison_v0(ostream &os)
{
if (meme_type<T, U>::value)
os << traits_noms<T>::nom() << " est le même type que "
<< traits_noms<U>::nom() << endl;
else
os << traits_noms<T>::nom() << " n'est pas le même type que "
<< traits_noms<U>::nom() << endl;
}
|
Une autre écriture, plus féconde, repose sur une approche
inspirée de std::bind1st(),
de
STL (aujourd'hui déprécié, mais ça reste un chic exemple),
qui permet de transformer une opération binaire (deux opérandes)
en une opération unaire (un seul opérande) en liant le
1er paramètre à une valeur
donnée (ou, ici, à un type donné). On aurait pu faire
de même avec le 2e
paramètre, évidemment.
Ce faisant, il devient possible de réduire le savoir requis par
la fonction évaluant les comparaisons de type. Pour les fins de
l'exemple proposé ici, le foncteur statique binaire transformé
en foncteur statique unaire est meme_type
(voir le programme principal, plus bas, pour
un exemple d'utilisation).
|
template <class T, template <class, class> class Pred>
struct static_bind1st
{
template <class U>
struct eval
{
enum { value = Pred<T, U>::value };
};
};
template <class T, class P>
void message_de_comparaison_v1(ostream &os)
{
if (P::eval<T>::value)
os << traits_noms<T>::nom() << " : pareil" << endl;
else
os << traits_noms<T>::nom() << " : pas pareil" << endl;
}
|
L'un des très beaux exemples de métaprogrammation
est donné par les listes
de types, que nous utiliserons un peu plus bas.
|
//
// Les listes de types d'Alexandrescu
//
class Vide {};
template <class T, class Q>
struct type_list
{
using tete = T;
using queue = Q;
};
|
Un algorithme statique simple pour trouver un type donné dans
une liste de types est exprimé à droite.
|
template <class TList, class T>
struct est_dans_v0;
template <class T, class Q>
struct est_dans_v0<type_list<T, Q>, T>
{
enum { value = true };
};
template <class T, class Q, class U>
struct est_dans_v0<type_list<T, Q>, U>
{
enum { value = est_dans_v0<Q, U>::value };
};
template <class T>
struct est_dans_v0<Vide, T>
{
enum { value = false };
};
|
Un algorithme statique équivalent, mais plus flexible car combinant
un foncteur statique à titre de prédicat et un algorithme
de recherche statique à l'aide d'un prédicat, est présenté
à droite. Avec d'autres prédicats statiques, il deviendrait
possible d'exprimer des recherches encore plus complexes.
|
template <class TList, class Pred>
struct static_find_if;
template <class T, class Q, class Pred>
struct static_find_if<type_list<T, Q>, Pred>
{
enum { value = Pred::eval<T>::value || static_find_if<Q, Pred>::value };
};
template <class Pred>
struct static_find_if<Vide, Pred>
{
enum { value = false };
};
template <class TList, class T>
struct est_dans_v1
{
enum { value = static_find_if<TList, static_bind1st<T, meme_type> >::value };
};
|
Les deux exemples d'« appels » aux algorithmes
statiques de recherche présentés ici montrent que, pour
le code client, les deux approches sont syntaxiquement équivalentes.
évidemment, puisque le fruit de l'une comme de l'autre est une
constante statique, les performances à l'exécution seront
précisément les mêmes dans chaque cas.
|
template <class TList, class T>
void message_de_recherche_v0(ostream &os)
{
if (est_dans_v0<TList, T>::value)
os << traits_noms<T>::nom() << " est dans la liste" << endl;
else
os << traits_noms<T>::nom() << " n'est pas dans la liste" << endl;
}
template <class TList, class T>
void message_de_recherche_v1(ostream &os)
{
if (est_dans_v1<TList, T>::value)
os << traits_noms<T>::nom() << " est dans la liste" << endl;
else
os << traits_noms<T>::nom() << " n'est pas dans la liste" << endl;
}
|
Un exemple académique de code client démontrant le bon
fonctionnement de tout cela est présenté à droite.
|
#include <iostream>
using namespace std;
int main()
{
message_de_comparaison_v0<char, char>(cout);
message_de_comparaison_v0<char, signed char>(cout);
message_de_comparaison_v0<char, unsigned char>(cout);
typedef static_bind1st<char, meme_type> static_predicate_type;
message_de_comparaison_v1<char, static_predicate_type>(cout);
message_de_comparaison_v1<signed char, static_predicate_type>(cout);
message_de_comparaison_v1<unsigned char, static_predicate_type>(cout);
using types_caracteres_1_byte = type_list<
char, type_list<
signed char, type_list<
unsigned char, Vide
>
>
>;
message_de_recherche_v0<types_caracteres_1_byte, char>(cout);
message_de_recherche_v0<types_caracteres_1_byte, signed char>(cout);
message_de_recherche_v0<types_caracteres_1_byte, unsigned char>(cout);
message_de_recherche_v0<types_caracteres_1_byte, wchar_t>(cout);
message_de_recherche_v1<types_caracteres_1_byte, char>(cout);
message_de_recherche_v1<types_caracteres_1_byte, signed char>(cout);
message_de_recherche_v1<types_caracteres_1_byte, unsigned char>(cout);
message_de_recherche_v1<types_caracteres_1_byte, wchar_t>(cout);
}
|
En espérant que le tout vous soit utile...