|
Version
C++ 17 |
Version
C++ 11 |
Version
C++ 03 |
Cet exemple utilisera des
listes de types, idée brillante d'Andrei
Alexandrescu.
Dans la version
C++ 03,
j'ai complété le code de base avec quelques macros (oui,
je sais...), pour alléger l'écriture, de même qu'avec un accesseur simple
nommé static_head<TL> permettant d'obtenir
aisément le type en tête de la
liste de types TL.
En passant par des
templates
variadiques, la version
C++ 11
évite le recours aux macros.
Notez que le type maison type_list pourrait
être remplacé, pour l'essentiel, par
std::tuple.
|
#include <iostream>
#include <type_traits>
using namespace std;
template <class...>
struct type_list;
template <class>
struct static_head;
template <class T, class ... Q>
struct static_head<type_list<T, Q...>> {
using type = T;
};
template <class TL>
using static_head_t = typename static_head<TL>::type;
|
#include <iostream>
#include <type_traits>
using namespace std;
template <class...>
struct type_list;
template <class>
struct static_head;
template <class T, class ... Q>
struct static_head<type_list<T, Q...>> {
using type = T;
};
template <class TL>
using static_head_t = typename static_head<TL>::type;
|
#include <iostream>
#include <type_traits>
using namespace std;
template <class, class>
struct type_list;
class Vide {};
template <class>
struct static_head;
template <class T, class Q>
struct static_head<type_list<T,Q> > {
using type = T;
};
#define MAKE_TLIST(T,Q) type_list<T,Q>
#define MAKE_TLIST_0(T) \
type_list<T,Vide>
#define MAKE_TLIST_1(T0,T1) \
type_list<T1,MAKE_TLIST_0(T0)>
#define MAKE_TLIST_2(T0,T1,T2) \
type_list<T2,MAKE_TLIST_1(T0,T1)>
#define MAKE_TLIST_3(T0,T1,T2,T3) \
type_list<T3,MAKE_TLIST_2(T0,T1,T2)>
#define MAKE_TLIST_4(T0,T1,T2,T3,T4) \
type_list<T4,MAKE_TLIST_3(T0,T1,T2,T3)>
|
Pour convertir une constante entière N en type, et
par la suite raisonner sur
des listes de tels types, j'emprunterai la technique proposée à droite à
Dave Abrahams.
Dans la la version
C++ 11,
les types integral_constant,
true_type et false_type sont standards,
tout simplement.
J'ai utilisé template <auto> pour la
version C++ 17.
|
template <auto N>
struct constant : integral_constant<decltype(N), N> {
};
|
template <int N>
struct int_ : integral_constant<int, N> {
};
|
template <class T, T val>
struct integral_constant {
using type = T;
static const constexpr T value = val;
};
template <int N>
struct int_ : integral_constant<int,N> { };
struct true_type : integral_constant<bool, true> { };
struct false_type : integral_constant<bool, false> { };
|
L'algorithme static_contains<TL,Pred>::value
sera true seulement s'il existe au
moins un type T dans TL
pour lequel Pred<T>::value s'avère.
Ici comme dans les autres algorithmes manipulant des
listes de types, remarquez le déplacement de la liste de types dans l'ordre des paramètres du
template. En effet,
les paramètres variadiques doivent apparaître en dernier dans une
séquence de paramètres à un
template,
ce qui nous amène à structurer les algorithmes dans leur déclinaison
C++ 11
de manière à ce que tel soit bel et bien le cas.
|
template <template <class> class Pred, class TL>
struct static_contains;
template <template <class> class Pred, class TL>
using static_contains_t = typename static_contains<Pred, TL>::type;
template <class T, class ... Q, template <class> class Pred>
struct static_contains<Pred, type_list<T, Q...>>
: constant<Pred<T>::value ||
static_contains<Pred, type_list<Q...>>::value>
{
};
template <template <class> class Pred>
struct static_contains<Pred, type_list<>> : false_type {
};
|
template <template <class> class Pred, class TL>
struct static_contains;
template <template <class> class Pred, class TL>
using static_contains_t = typename static_contains<Pred, TL>::type;
template <class T, class ... Q, template <class> class Pred>
struct static_contains<Pred, type_list<T, Q...>>
: integral_constant<bool, Pred<T>::value ||
static_contains<Pred, type_list<Q...>>::value>
{
};
template <template <class> class Pred>
struct static_contains<Pred, type_list<>> : false_type {
};
|
template <class TL, template <class> class Pred>
struct static_contains;
template <class T, class Q, template <class> class Pred>
struct static_contains<type_list<T, Q>, Pred> :
integral_constant<bool, Pred<T>::value ||
static_contains<Q, Pred>::value> {
};
template <class T, template <class> class Pred>
struct static_contains<type_list<T, Vide>, Pred>
: integral_constant<bool, Pred<T>::value> {
};
|
L'algorithme static_for_each<TL> décrit
ici est particulier, au sens où il chevauche la métaprogrammation et la
programmation conventionnelle. En effet, une instance de
static_for_each<TL> expose une méthode de classe
execute()acceptant en paramètre une opération
oper de type Op et l'appliquant sur
chaque type T de TL.
|
template <class TList>
struct static_for_each;
template <class T, class ... Q>
struct static_for_each<type_list<T, Q...>> {
template <class Op>
static void execute(Op oper) {
oper.execute<T>();
static_for_each<type_list<Q...>>::execute(oper);
}
};
template <>
struct static_for_each<type_list<>> {
template <class Op>
static void execute(Op) {
}
};
|
template <class TList>
struct static_for_each;
template <class T, class ... Q>
struct static_for_each<type_list<T, Q...>> {
template <class Op>
static void execute(Op oper) {
oper.execute<T>();
static_for_each<type_list<Q...>>::execute(oper);
}
};
template <>
struct static_for_each<type_list<>> {
template <class Op>
static void execute(Op) {
}
};
|
template <class TList>
struct static_for_each;
template <class T, class Q>
struct static_for_each<type_list<T, Q>> {
template <class Op>
static void execute(Op oper) {
oper.execute<T>();
static_for_each<Q>::execute(oper);
}
};
template <class T>
struct static_for_each<type_list<T, Vide>> {
template <class Op>
static void execute(Op oper) {
oper.execute<T>();
}
};
|
L'algorithme static_accumulate<TL,F,Init>
applique le foncteur statique binaire F
à chaque élément de TL, relayant chaque fois
en tant que premier paramètre la valeur accumulée jusque là. Initialement,
la valeur accumulée est décrite par le type Init.
|
template <template <class, class> class F, class Init, class TList>
struct static_accumulate;
template <template <class, class> class F, class Init, class TL>
using static_accumulate_t = typename static_accumulate<F, Init, TL>::type;
template <template <class, class> class F, class Init, class T, class ... Q>
struct static_accumulate<F, Init, type_list<T, Q...>> {
using type =
typename F<T, static_accumulate_t<F, Init, type_list<Q...>>>::type;
};
template <template <class, class> class F, class Init, class T>
struct static_accumulate<F, Init, type_list<T>> {
using type = typename F<T, Init>::type;
};
|
template <template <class, class> class F, class Init, class TList>
struct static_accumulate;
template <template <class, class> class F, class Init, class TL>
using static_accumulate_t = typename static_accumulate<F, Init, TL>::type;
template <template <class, class> class F, class Init, class T, class ... Q>
struct static_accumulate<F, Init, type_list<T, Q...>> {
using type =
typename F<T, static_accumulate_t<F, Init, type_list<Q...>>>::type;
};
template <template <class, class> class F, class Init, class T>
struct static_accumulate<F, Init, type_list<T>> {
using type = typename F<T, Init>::type;
};
|
template <class TList, template <class, class> class F, class Init>
struct static_accumulate;
template <class T, class Q, template <class, class> class F, class Init>
struct static_accumulate<type_list<T, Q>, F, Init> {
using type = typename F<
T, typename static_accumulate<Q, F, Init>::type
>::type;
};
template <class T, template <class, class> class F, class Init>
struct static_accumulate<type_list<T, Vide>, F, Init> {
using type = typename F<T, Init>::type;
};
|
Le foncteur statique not_ appliqué à un
prédicat unaire statique Pred expose un type
générique type<T> tel que
not_<Pred>::type<T>::value s'avère seulement si
Pred<T>::value est false.
|
template <template <class> class Pred>
struct not_ {
template <class T>
struct type : constant<!Pred<T>::value> {
};
};
|
template <template <class> class Pred>
struct not_ {
template <class T>
struct type : integral_constant<bool, !Pred<T>::value> {
};
};
|
template <template <class> class Pred>
struct not_ {
template <class T>
struct type : integral_constant<!Pred<T>::value> {
};
};
|
Le prédicat statique is_int<T>::value
s'avère seulement si T est un
int_<N> pour un certain entier statique N.
|
template <class>
struct is_int : false_type {
};
template <int N>
struct is_int<constant<N>> : true_type {
};
|
template <class>
struct is_int : false_type {
};
template <int N>
struct is_int<int_<N>> : true_type {
};
|
template <class>
struct is_int : false_type {
};
template <int N>
struct is_int<int_<N>> : true_type {
};
|
Toute instance de la classe afficher_nom
offre un une méthode executer()
générique sur la base d'un type T qui projette
sur un flux le nom du type T.
|
#include <typeinfo>
class afficher_nom {
ostream &os;
public:
afficher_nom(ostream &os) : os{ os } {
}
template <class T>
void execute() {
os << typeid(T).name() << endl;
}
};
|
#include <typeinfo>
class afficher_nom {
ostream &os;
public:
afficher_nom(ostream &os) : os{ os } {
}
template <class T>
void execute() {
os << typeid(T).name() << endl;
}
};
|
#include <typeinfo>
class afficher_nom {
ostream &os;
public:
afficher_nom(ostream &os) : os{os} {
}
template <class T>
void execute() {
os << typeid(T).name() << endl;
}
};
|
Le foncteur statique binaire static_somme<A,B>
expose un type type représentant la
somme des valeurs de A et de
B.
|
template <class A, class B>
struct static_somme : constant<A::value + B::value> {
};
|
template <class A, class B>
struct static_somme : int_<A::value + B::value> {
};
|
template <class A, class B>
struct static_somme {
using type = int_<A::value + B::value>;
};
|
Le foncteur statique binaire
static_produit<A,B> expose un type type représentant le
produit des valeurs de A et de
B.
|
template <class A, class B>
struct static_produit : constant<A::value * B::value> {
};
|
template <class A, class B>
struct static_produit : int_<A::value * B::value> {
};
|
template <class A, class B>
struct static_produit {
using type = int_<A::value * B::value>;
};
|
Le foncteur statique binaire static_min<A,B>
expose un type type
représentant le minimum des valeurs de A et de
B.
|
template <class A, class B>
struct static_min : constant<(A::value < B::value) ? A::value : B::value> {
};
|
template <class A, class B>
struct static_min : int_<(A::value < B::value) ? A::value : B::value> {
};
|
template <class A, class B>
struct static_min {
using type = int_<(A::value < B::value)? A::value :B::value>;
};
|
Le foncteur statique binaire static_max<A,B>
expose un type type représentant le
maximum des valeurs de A et de
B.
|
template <class A, class B>
struct static_max : constant<(A::value > B::value) ? A::value : B::value> {
};
|
template <class A, class B>
struct static_max : int_<(A::value > B::value) ? A::value : B::value> {
};
|
template <class A, class B>
struct static_max {
using type = int_<(A::value > B::value)? A::value :B::value>;
};
|
La fonction
afficher_infos_types_entiers<TL>(os) projette sur le flux
os des informations sur les valeurs colligées dans
TL.
|
template <class TList>
void afficher_infos_types_entiers(ostream &os) {
using tete = static_head_t<TList>;
os << "Somme des valeurs: "
<< static_accumulate_t<static_somme, int_<0>, TList>::value << endl;
os << "Produit des valeurs: "
<< static_accumulate_t<static_produit, int_<1>, TList>::value << endl;
os << "Plus petite valeur: "
<< static_accumulate_t<static_min, tete,TList>::value << endl;
os << "Plus grande valeur: "
<< static_accumulate_t<static_max, tete, TList>::value << endl;
}
|
template <class TList>
void afficher_infos_types_entiers(ostream &os) {
using tete = static_head_t<TList>;
os << "Somme des valeurs: "
<< static_accumulate_t<static_somme, int_<0>, TList>::value << endl;
os << "Produit des valeurs: "
<< static_accumulate_t<static_produit, int_<1>, TList>::value << endl;
os << "Plus petite valeur: "
<< static_accumulate_t<static_min, tete,TList>::value << endl;
os << "Plus grande valeur: "
<< static_accumulate_t<static_max, tete, TList>::value << endl;
}
|
template <class TList>
void afficher_infos_types_entiers(ostream &os) {
using tete = typename
static_head<TList>::type;
os << "Somme des valeurs: " << typename static_accumulate<
TList, static_somme, int_<0>
>::type::value << endl;
os << "Produit des valeurs: " << typename static_accumulate<
TList, static_produit, int_<1>
>::type::value << endl;
os << "Plus petite valeur: " << typename static_accumulate<
TList, static_min, tete
>::type::value << endl;
os << "Plus grande valeur: " << typename static_accumulate<
TList, static_max, tete
>::type::value << endl;
}
|
Le type static_afficher_infos_types_entiers<TL>
expose une méthode de classe execute(os)
appliquant afficher_infos_liste_entiers(os)
sur TL.
|
template <class TList>
struct static_afficher_infos_types_entiers {
static void execute(ostream &os) {
afficher_infos_types_entiers<TList>(os);
}
};
|
template <class TList>
struct static_afficher_infos_types_entiers {
static void execute(ostream &os) {
afficher_infos_types_entiers<TList>(os);
}
};
|
template <class TList>
struct static_afficher_infos_types_entiers {
static void execute(ostream &os) {
afficher_infos_types_entiers<TList>(os);
}
};
|
Le type static_no_op expose une méthode
execute() générique sur la base d'un type T
et dont le rôle est... de ne rien faire, donc d'être un simple
no-op.
|
struct static_no_op {
template <class T>
static void execute(const T &) {
}
};
|
struct static_no_op {
template <class T>
static void execute(const T &) {
}
};
|
struct static_no_op {
template <class T>
static void execute(const T &) {
}
};
|
La fonction générique afficher_infos<TL>(os)
décrit les types de TL sur
os. Elle réalise aussi une série d'opérations mathématiques simples
sur les types de TL, mais seulement si tous
les types de TL sont des instanciations de
int_.
|
template <class TList>
void afficher_infos(ostream &os) {
static constexpr const bool has_non_int_type = static_contains_t<
typename not_<is_int>::type, TList
>::value;
static_for_each<TList>::execute(afficher_nom{ os });
if (has_non_int_type)
os << "\tImpossible de cumuler cette liste sur des entiers" << endl;
conditional_t<
has_non_int_type, static_no_op, static_afficher_infos_types_entiers<TList>
>::execute(os);
os << endl;
}
|
template <class TList>
void afficher_infos(ostream &os) {
static constexpr const bool has_non_int_type = static_contains_t<
typename not_<is_int>::type, TList
>::value;
static_for_each<TList>::execute(afficher_nom{ os });
if (has_non_int_type)
os << "\tImpossible de cumuler cette liste sur des entiers" << endl;
conditional_t<
has_non_int_type, static_no_op, static_afficher_infos_types_entiers<TList>
>::execute(os);
os << endl;
}
|
template <class TList>
void afficher_infos(ostream &os) {
using has_non_int_type = typename
static_contains<
TList, typename not_<is_int>::type
>;
static_for_each<TList>::execute(afficher_nom{os});
if (has_non_int_type::value)
os << "\tImpossible de cumuler cette liste sur des entiers" << endl;
typename conditional<
has_non_int_type::value,
static_no_op,
static_afficher_infos_types_entiers<TList>
>::type::execute(os);
os << endl;
}
|
Le programme de test démontre que le tout fonctionne avec une liste
d'entiers comme avec une liste ne contenant que des simples types
primitifs.
|
int main() {
using liste_A = type_list<int, short, double, char>;
using liste_B = type_list<constant<2>, constant<3>, constant<5>> ;
afficher_infos<liste_A>(cout);
afficher_infos<liste_B>(cout);
}
|
int main() {
using liste_A = type_list<int, short, double, char>;
using liste_B = type_list<int_<2>, int_<3>, int_<5>> ;
afficher_infos<liste_A>(cout);
afficher_infos<liste_B>(cout);
}
|
int main() {
using liste_A = MAKE_TLIST_3(int,short,double,char);
using liste_B = MAKE_TLIST_2(int_<2>, int_<3>, int_<5>);
afficher_infos<liste_A>(cout);
afficher_infos<liste_B>(cout);
}
|
Quelques liens pour enrichir le propos.