image1

Image originale ici

L'introduction de C++11 apporte dans son jeu de nouvelles fonctionnalités les variadic templates, qui permettent de spécifier des paramètres de patrons à géométrie variable. Voici un exemple qui illustre l'avantage des variadic templates comparé aux méthodes "classiques", pour pallier à un problème courant : utiliser une même fonction, et ce quel que soit son nombre de paramètres.

Jusqu'à présent (entendre : avant C++11) plusieurs techniques permettaient éventuellement de mimer ce comportement : macro, stdarg (basé sur des macros et non typé), surcharge de fonctions, paramètres par défaut, templates.

A titre d'exemple, une fonction classique telle que printf, basée sur stdarg, n'offre pas de “typage sécurisé” (type-safe), au bénéfice d'une méthode prenant un nombre variable de paramètres.

D'autre part, l'utilisation de macros va à l'encontre des règles de portée et de types (sans parler de débogages qui peuvent se révéler cauchemardesques...) ; pour ma part, j'évite d'avoir recours au pré-processeur autant que possible.

L'utilisation de paramètres par défaut peut se révéler dangereuse, et la surcharge est pénible à maintenir, même combiné avec des templates.

Prenons comme exemple l'implémentation du patron de conception Singleton, sous la forme d'une classe générique, et donc réutilisable ; intéressons-nous à une méthode createSingleton() (imaginons qu'elle soit invoquée par une mécanique interne au Singleton) qui instancie notre objet :

template <typename Type> class Singleton {
    static Type* createSingleton() {
        return new Type;
    }
};

Dans le code ci-dessus, la classe fonctionne très bien lorsque les classes qui en héritent ont un constructeur ne prenant pas d'arguments. Mais si on veut utiliser une classe qui nécessite des arguments (par exemple une classe qui a une référence en membre), il est nécessaire d'enrichir le jeu de méthodes createSingleton() ; or utiliser une macro n'est pas jouable. De plus, utiliser une surcharge combinée à des templates "classiques" nécessite que chaque classe héritée fournisse un constructeur par défaut :

template <typename Type> class Singleton {
    static Type* createSingleton() { return new Type; }

    static template <typename Type>
    Type* createSingleton(ArgType const& arg) { return new Type(arg); }

    static template <typename Type>
    Type* createSingleton(ArgOne const& one, ArgTwo const& two)
    { return new Type(one, two); }
};

class Foo: public Singleton<Foo> {
    private:
    friend Singleton<Foo>;
    Foo() = delete; // Problème ! Singleton requière ce constructeur !
    Foo(size_t); // Seul constructeur valide.
};

Une solution à ce problème est d'utiliser une “Policy”, une structure spécifiée en argument de patron du Singleton, et à qui est délégué l’instanciation ; c'est cependant une opération qui peut s'avérer complexe et lourde en code. C'est ici qu'intervient la notion de variadic template : implémenter la méthode createSingleton() paramétrée par template comme avant, mais où les arguments des templates eux-même sont variables :

template <typename Type> class Singleton {
    template <typename ... All>
    Type* createSingleton(All const& ... arguments) {
        return new Type(arguments ...);
    }
};

Et voilà ! L'ajout des ... utilise automagiquement cette fonctionnalité, qui résout bien l'ensemble du problème : on conserve une caractéristique type-safe, on permet aux classes dérivées d'implémenter les constructeurs qu'elles veulent, et il n'est pas besoin d'impacter Singleton dans les cas où une nouvelle classe hérite de ce patron de conception.

Cette utilisation n'est pas le seul avantage apporté par les variadic templates ; comme évoqué plus haut, cela est également utilisable pour les classes, par exemple déclarer une classe étant flexible sur les polices de comportement :

template <typename ... Policies>
class PolicyBased: public Policies ... {};

Ou encore pour utiliser des objets selon la même idée que les tuples , mais de façon plus permissive :

class Container
{
    template <typename ... Types>
    Container(Types const&& ... arguments) {}
};

Et comme beaucoup de fonctionnalités du C++, de nouvelles façons de les utiliser vont fleurir avec le temps. D'ailleurs, un autre apport de C++11 que nous verrons, tout aussi appréciable, est la possibilité de définir des valeurs littérales.


Comments

comments powered by Disqus