[Histoire du C++] Les templates : des macros C aux concepts

Article original : [History of C++] Templates: from C-style macros to concepts | Belay the C++ (belaycpp.com)
Traductrice : Chloé Lourseyre

Introduction : les types paramétrés

Template est, en C++, le nom donn√© √† la fonctionnalit√© (ou plut√īt au groupe de fonctionnalit√©s, puisque le mot est utilis√© dans plusieurs contextes) qui impl√©mente les types param√©tr√©s.

La notion de types param√©tr√©s (ou parametrized types dans sa d√©nomination originale) est tr√®s importante dans la programmation moderne. Elle consiste √† utiliser un type en tant que param√®tre d’une fonctionnalit√©, de mani√®re √† ce que cette fonctionnalit√© puisse √™tre utilis√©e avec plusieurs types diff√©rents, et ce de la m√™me mani√®re qu’on utilise une fonctionnalit√© avec diff√©rentes valeurs.

L’exemple le plus simple est avec std::vector. Quand vous d√©clarez un vecteur comme ceci : std::vector<int> foo;, le type int est param√©tr√©. Vous auriez tout aussi bien pu mettre un autre type, comme doublevoid* ou une classe d√©finie par vous.

C’est une mani√®re de faire de la m√©taprogrammation, c’est-√†-dire √©crire un programme dont le but est de modifier les donn√©es d’un autre programme (ou d’une autre partie de son programme).

Pour le reste de l’article (y compris la partie 2), j’utiliserai le mot ¬ę¬†template¬†¬Ľ pour renvoyer √† la fois au concept de types param√©tr√©s et √† son impl√©mentation en C++ (sauf dans les cas o√Ļ je voudrais faire explicitement la distinction).

Avant les templates

Avant la création des templates, au début du C++, on devait écrire des macros de style C pour les émuler.

Une manière de faire était la suivante :

foobar.h

void foobar(FOOBAR_TYPE my_val);

foobar.cpp

void foobar(FOOBAR_TYPE my_val)
{
    // do stuff
}

main.cpp

#define FOOBAR_TYPE int
#include "foobar.h"
#include "foobar.cpp" // Only do this in a source file
#undef FOOBAR_TYPE
 
#define FOOBAR_TYPE double
#include "foobar.h"
#include "foobar.cpp" // Only do this in a source file
#undef FOOBAR_TYPE
 
int main()
{
    int toto = 42;
    double tata = 84;
    foobar(toto);
    foobar(tata);
}

Mais ne faites pas √ßa chez vous ! Ce n’est pas quelque chose qu’on veut faire de nos jours (surtout la partie #include <foobar.cpp>). Aussi, notez que ce code utilise la fonctionnalit√© des surcharges de fonctions, propre au C++, et que donc ce code ne compile pas en C.

De notre point de vue moderne, cela peut sembler tr√®s limit√© et sujet √† erreurs. Mais la chose int√©ressante √† retenir est que puisque les templates √©taient utilis√©s avec des macros avant m√™me le C++, elles pouvaient √™tre utilis√©es au d√©but du C++ et donc permettre √† l’√©quipe de design du langage de gagner de l’exp√©rience avant son impl√©mentation r√©elle.

Timing

Les templates ont √©t√© introduits avec la version 3.0 du langage, en octobre 1991. Dans The Design and Evolution of C++, Stroustrup r√©v√®le que c’√©tait une erreur de les introduire aussi tard, et qu’en r√©trospective il aurait √©t√© pr√©f√©rable de l’introduire dans la version 2.0 (juin 1989) quitte √† mettre de c√īt√© d’autres fonctionnalit√©s moins importantes, comme l’h√©ritage multiple :

Aussi, ajouter l’h√©ritage multiple dans la Version 2.0 √©tait une erreur. L’h√©ritage multiple a sa place en C++, mais est beaucoup moins importante que les types param√©tr√©s ‚ÄĒ et pour certaines personnes, les types param√©tr√©s le sont encore moins que les exceptions.
‚ÄĒ Bjarne Stroustrup, The Design and Evolution of C++, chapitre 12: Multiple Inheritance, ¬ß1 – Introduction

Avec le recul d’aujourd’hui, il est clair que Stroustrup avait raison et que les template ont impact√© le paysage du C++ beaucoup, beaucoup plus que l’h√©ritage multiple.

Cet ajout est arriv√© tard car il √©tait tr√®s chronophage pour les concepteurs d’explorer les designs et les soucis potentiels d’impl√©mentation.

Besoins et objectifs

Le besoin originel pour les templates √©tait de param√©trer les classes de conteneurs. Pour ce travail, les macros √©taient trop limit√©es. Elles ne respectaient pas les p√©rim√®tres de nommage et interagissaient tr√®s mal avec les outils (en particulier les debuggers). Avant les templates C++, il √©tait tr√®s difficile de maintenir du code qui utilisait des type param√©tr√©s, n√©cessitait de code dans un niveau d’abstraction tr√®s bas et on devait ajouter chaque instance de type param√©tr√© √† la main.

Les premières inquiétudes concernant les templates étaient les suivantes : est-ce que les templates seraient aussi faciles à utiliser que les objets codés à la main ? Est-ce que les temps de compilation et de link seraient significativement impactés ? Et est-ce que ce serait facilement portable ?

Le processus de développement des templates

La derni√®re fois nous avions vu l’origine des templates, l’id√©e de d√©part et la note d’intention. Cette semaine nous allons voir comment les template ont √©t√© construit et comment ils √©voluent encore aujourd’hui.

Syntaxe

Les chevrons

Concevoir la syntaxe d’une fonctionnalit√© n’est pas ais√© et n√©cessite beaucoup de questionnement.

Le choix des chevrons <...> pour les param√®tres de template a √©t√© fait parce que m√™me si les parenth√®ses auraient √©t√© plus pratique pour les analyseurs, elles sont tr√®s utilis√©es en C++, et les chevrons sont plus plaisants √† lire dans ce contexte.

Ceci dit, cela pose des problèmes pour les chevrons imbriqués, comme ceci :

List<List<int>> a;

Dans ce petit bout de code, au d√©but du C++, vous auriez eu une erreur de compilation car les chevrons fermant >> auraient √©t√© per√ßu comme l’op√©rateur de flux operator>>() et non comme deux chevrons fermants.

Depuis le C++141, une motion lexicale a été ajoutée pour prendre cela en compte et ne plus le considérer comme une erreur de compilation.

L’argument de template

Au d√©part, l’argument de template aurait √©t√© plac√© juste apr√®s l’objet qu’on template :

class Foo<class T>
{
    // ...
};

Mais cela posait deux problèmes :

  • C’est assez dur √† lire pour les analyseurs automatiques et pour les humains. Comme l’indication qu’il s’agit d’un template est imbriqu√© dans la d√©finition de l’objet, c’est un peu difficile √† d√©tecter.
  • Dans le cas des fonctions templat√©es, le type templat√© peut √™tre utilis√© avant d’√™tre d√©clar√©, comme ceci: T at<class T>(const std::vector<T>& v, size_t index) { return v[index]; }. Comme T est le type de retour il est vu (par les analyseurs automatiques) avant m√™me qu’on sache qu’il s’agit d’un type templat√©.

Ces deux probl√®mes sont r√©gl√©s si on d√©clare le template avant l’objet, comme ceci :

template<class T> class Foo
{
    // ...
};
 
template<class T> T at(const std::vector<T>& v, size_t index) { return v[index]; }

Et c’est ce qui a √©t√© retenu.

Les contraintes sur les paramètres de template

En C++, les contraintes sur les paramètres des templates sont implicites2

Le dilemme suivant est apparu √† la cr√©ation des templates : est-ce qu’on doit rendre les contraintes explicites ou pas ?

Un exemple de contrainte explicite a été proposé comme ceci :

template < class T {
        int operator==(const T&, const T&); 
        T& operator=(const T&);
        bool operator<(const T&, int);
    };
>
class Foo {
    // ...
};

Mais cela a √©t√© jug√© trop verbeux et il aurait fallu √©crire plus de templates pour le m√™me nombre de features au final. De plus, cela limite un peu trop fort les possibilit√©s des classes qu’on impl√©mente, excluant des impl√©mentations qui auraient √©t√© tout √† fait correcte sans elles3.

Cependant, l’id√©e d’avoir des contraintes explicites n’a pas √©t√© abandonn√©e, c’est juste que les exprimer ainsi n’√©tait pas la bonne mani√®re de faire.

Une autre mani√®re de faire (qui a √©t√© envisag√©e) a √©t√© via des classes d√©riv√©es. En sp√©cifiant que tel template doit d√©river de telle classe, on obtient un moyen explicite d’ajouter des contraintes :

template <class T>
class TBase {
    int operator==(const T&, const T&); 
    T& operator=(const T&);
    bool operator<(const T&, int);
};
 
template <class T : TBase>
class Foo {
    // ...
};

Cependant cette m√©thode cr√©√© plus de probl√®mes que cela n’en r√®gle. Les d√©veloppeurs sont, avec cette m√©thode, encourag√©s √† exprimer les contraintes en tant que classes, menant √† une sur-utilisation de l’h√©ritage. Il y a une perte d’expressivit√© et de sens s√©mantique, parce que ¬ę¬†T doit √™tre comparable √† une int¬†¬Ľ devient ¬ę¬†T doit h√©riter de TBase¬†¬Ľ. De plus, vous ne pouvez pas exprimer des contraintes sur des types qui ne peuvent pas avoir de classe m√®re, comme int ou double.

Ce sont les raisons pour lesquelles nous n’avons pas eu de moyen d’exprimer des contraintes sur les param√®tres de template pendant tr√®s longtemps4.

Mais tout vient √† point √† qui sait attendre, et le d√©bat a √©t√© ranim√© √† la fin des ann√©es 2010, ce qui a men√© √† la cr√©ation des Concepts en C++20 (c.f. √Čvolutions modernes ‚Äď Concepts plus bas).

La g√©n√©ration d’objets templat√©s

La mani√®re dont les templates sont compil√©s est assez simple : pour chaque jeu de param√®tres de templates (pour un objet templat√© donn√©), le compilateur va g√©n√©r√© autant d’impl√©mentations de cet objet en utilisant explicitement ce jeu de param√®tres.

Donc, écrire ceci :

template <class T> class Foo { /* ... do things with T ... */ };
template <class T, class U> class Bar { /* ... do things with T  and U... */ };
 
Foo<int> foo1;
Foo<double> foo2;
Bar<int, int> bar1;
Bar<int, double> bar2;
Bar<double, double> bar3;
Bar< Foo<int>, Foo<long> > bar4;

Est la m√™me chose qu’√©crire cela :

class Foo_int { /* ... do things with int ... */ };
class Foo_double { /* ... do things with double ... */ };
class Foo_long { /* ... do things with long ... */ };
class Bar_int_int { /* ... do things with int  and int... */ };
class Bar_int_double { /* ... do things with int  and double... */ };
class Bar_double_double { /* ... do things with double  and double... */ };
class Bar_Foo_int_Foo_long { /* ... do things with Foo_int  and Foo_long... */ };
 
Foo_int foo1;
Foo_double foo2;
Bar_int_int bar1;
Bar_int_double bar2;
Bar_double_double bar3;
Bar_Foo_int_Foo_long bar4;

… sauf que c’est plus verbeux et moins g√©n√©rique.

Classes templatées

√Ä la base, les templates ont √©t√© imagin√©s pour les classes, en particulier pour l’impl√©mentation de conteneurs standards. Ils ont √©t√© pens√©s pour √™tre aussi simples √† utiliser que les classes standardes et aussi performantes que les macros. Ces deux faits ont √©t√© d√©cid√©s pour que les tableaux bas niveaux puissent √™tre abandonn√©s quand ils n’√©taient pas sp√©cifiquement utiles (comme en programmation tr√®s bas niveau) et que les conteneurs templat√©s soient pr√©f√©r√©s pour les plus hauts niveaux.

En plus des paramètres typés, les templates peuvent avoir des paramètres non-typés, comme suit :

template <class T, int Size>
class MyContainer {
    T m_collection[Size];
    int m_size;
public:
    MyContainer(): m_size(Size) {}
    // ...
};

Cela a √©t√© con√ßu pour permettre d’utiliser des conteneurs de taille statique. Avoir la taille directement dans le type du conteneur permet d’avoir une impl√©mentation plus performante.

class Foo;
 
int main()
{
    Foo[700] fooTable; // low-level container
    MyContainer<Foo, 700> fooCnt; // high-level container, as efficient as the previous one
}

Fonctions templatées

L’id√©e des fonctions templat√©es est venue directement du besoin d’avoir des m√©thodes de classe templat√©es et de l’id√©e selon laquelle les fonctions templat√©es sont dans la continuit√© logique des classes templat√©es.

Aujourd’hui, l’exemple le plus classique de fonction templat√© auquel on peut penser sont les algorithmes de la STL (std::find()std::find_first_of()std::merge(), etc.). M√™me √† sa cr√©ation, les algorithmes de la STL n’existaient pas, c’√©tait ce genre de fonctions qui a inspir√© les fonctions templat√©es (la plus symbolique √©tant sort()).

La principale problématique a été de savoir comment déduire les paramètres du template à partir des arguments et du type de retour sans avoir à les spécifier à chaque appel.

Dans ce contexte, il a √©t√© d√©cid√© que les arguments de templates pouvaient √† la fois √™tre d√©duits (quand cela est possible) et sp√©cifi√©s (quand c’est n√©cessaire). C’est extr√™mement utile pour sp√©cifier une valeur de retour, car celle-ci ne peut pas toujours √™tre d√©duite, comme dans l’exemple suivant :

template <class TTo, class TFrom>
TTo convert(TFrom val)
{
    return val;
}
 
int main()
{
    int val = 4;
    convert(val); // Error: TTo is ambiguous
    convert<double, int>(val) // Correct: TTo is double; TFrom is int
    convert<double>(val) // Correct: TTo is double; TFrom is int; 
}

Comme vous pouvez le voir ligne 12, les arguments de template peuvent être en partie (ou en intégralité) ignorés, en partant du dernier.

La mani√®re dont les templates sont g√©n√©r√©s (conf√®re √† la section La g√©n√©ration d’objets templat√©s ci-avant) fait qu’ils fonctionnent parfaitement bien avec la surcharge de fonctions. La seule subtilit√© intervient quand on a √† la fois des surcharges templat√©es et des surcharges non-templat√©es. Dans ce cas, les surcharges non-templat√©es sont prioritaires sur celles qui sont templat√©es, s’il y a une correspondance parfaite. Sinon, on prend la version templat√©e s’il est possible d’avoir une correspondance parfaite. Sinon, on r√©sout la surcharge comme une surcharge ordinaire.

Instanciation de templates

Au tout d√©but, l’instanciation explicite de templates n’√©tait pas vraiment envisag√©e. C’√©tait parce que cela pouvait cr√©er des probl√®mes complexes √† r√©soudre dans certaines circonstances sp√©cifiques. Par exemple : si deux parties (sans lien) d’un code indiquent chacune qu’elles veulent la m√™me instanciation d’un objet templat√©, ce qui aurait besoin d’√™tre fait sans r√©plication de code et sans g√™ner le link dynamique. C’est pourquoi au d√©but, il semblait pr√©f√©rable de n’avoir que des instanciations implicites.

La premi√®re instanciation automatique de template s’est d√©roul√©e ainsi : quand le linkeur est lanc√©, il cherche les instanciations de template manquantes. Il rappelle alors le compilateur pour qu’il les g√©n√®re. On relance alors le linkeur, qui cherche alors de nouveau les instanciations manquantes, et ainsi de suite jusqu’√† ce qu’on ait toutes les instanciations de template dont on a besoin.

Cependant, cette mani√®re de faire avait plusieurs soucis, notamment celui d’√™tre tr√®s peu performante (puisqu’on fait beaucoup de va-et-viens entre le compilateur et le linkeur).

C’est pour mitiger ce probl√®me que les instanciations de template explicite ont finalement √©t√© introduites.

Le d√©veloppement des instanciations de templates a eu beaucoup d’autres soucis et √©cueils, comme le point d’instanciation (aussi appel√© ¬ę¬†le probl√®me des noms¬†¬Ľ, c’est-√†-dire localiser pr√©cis√©ment √† quelle d√©claration les noms invoqu√©s dans un template font r√©f√©rence), les probl√®mes de d√©pendance, la r√©solution d’ambigu√Įt√©s, etc. Les √©voquer tous dans le d√©tail requerrait un article d√©di√©.

√Čvolutions modernes

Les templates sont une fonctionnalit√© qui a continu√© d’√©voluer alors m√™me qu’on entrait dans l’√®re moderne du C++ (qui commen√ßa avec le C++11).

Templates variadiques

Les templates variadiques sont des templates qui ont au moins un paquet de param√®tres. Un paquet de param√®tre (parameter pack en VO) est une mani√®re d’indiquer qu’une fonction ou un template a un nombre variable de param√®tres.

Par exemple, la fonction suivante utiliser un paquet de paramètres :

void foobar(int args...);

Et peut √™tre appel√©e avec n’importe quel nombre d’argument (mais toujours au moins 1) :

foobar(1);
foobar(42, 666);
foobar(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16);

Les templates variadiques permettent la même chose, mais avec des types différents pour chaque paramètre.

Avec cela, on peut écrire des fonctions beaucoup plus génériques. Par exemple :

#include <iostream>
 
struct MyLogger 
{
    static int log_counter;
 
    template<typename THead>
    static void log(const THead& h)
    {
        std::cout << "[" << (log_counter++) << "] " << h << std::endl;
    }
 
    template<typename THead, typename ...TTail>
    static void log(const THead& h, const TTail& ...t)
    {
        log(h);
        log(t...);
    }
};
 
int MyLogger::log_counter = 0;
 
int main()
{
    MyLogger::log(1,2,3,"FOO");
    MyLogger::log('f', 4.2);
}

Ce code génère la sortie suivante :

[0] 1
[1] 2
[2] 3
[3] FOO
[4] f
[5] 4.2

On peut ais√©ment supposer que la motivation derri√®re les templates variadiques (et les paquets de param√®tres) est de pouvoir impl√©menter des fonctions encore plus gen√©riques, au d√©triment parfois du volume de code g√©n√©r√© (en l’occurrence, dans l’exemple pr√©c√©dent, la classe MyLogger a 8 surcharges de la fonction log5).

Vous trouverez plus de d√©tails sur cette page : Parameter pack (since C++11) ‚Äď cppreference.com.

Concepts

Les concepts sont une fonctionnalit√© introduite en C++20 qui vise √† donner au d√©veloppeur une mani√®re de d√©clarer des contraintes sur les param√®tres d’un template. Cela m√®ne √† du code plus clair (avec un plus haut niveau d’abstraction) et des messages d’erreur moins abscons.

Par exemple, voici une déclaration de concept :

template<typename T_>
concept Addable = requires(T_ a, T_ b)
{
    a + b;
};

Et un exemple de son utilisation :

template<typename T_>
requires Addable<T_>
T_ foo(T_ a, T_ b);
 
template<typename T_>
T_ bar(T_ a, T_ b) requires Addable<T_>;
 
auto l = []<typename T_> requires Addable<T_> (T_ a, T_ b) {};

Avant cela, les erreurs de compilations liées à des templates était à peine lisible. Les concepts sont devenus (au cours des années précédant le C++20) une fonctionnalité très attendue.

Un bon aper√ßu des concept est trouvable sur le blog d’Oleksandr Koval : All C++20 core language features with examples | Oleksandr Koval‚Äôs blog (oleksandrkvl.github.io).

Guides de déduction

Les guides de déduction de templates sont une fonctionnalité du C++17 et sont des motifs qui, associés à un objet templaté qui indiquent au compilateur comment interpréter les paramètres (et leur type).

Par exemple :

template<typename T_>
struct Foo
{
  T_ t;
};
  
Foo(const char *) -> Foo<std::string>;
  
Foo foo{"A String"};

Dans ce code, l’objet foo est un Foo<std::string> et non un Foo<const char*> comme on pourrait √©ventuellement le penser. De ce fait, foo.t est une std::string. C’est gr√Ęce √† l’indication qu’on laisse sous la forme d’un guide de d√©duction, indiquant au compilateur que quand on utilise le type const char* on veut utiliser l’instanciation std::string du template.

C’est particuli√®rement utile pour des objets comme les vecteurs, qui peuvent ainsi avoir ce genre de constructeur :

template<typename Iterator> vector(Iterator b, Iterator e) -> vector<typename std::iterator_traits<Iterator>::value_type>;

De ce fait, si on appelle le constructeur du vecteur avec un it√©rateur, le compilateur comprendra qu’on veut non pas un vecteur d’it√©rateurs, mais un vecteur contenant des objets de m√™me type que celui pointe par l’it√©rateur.

Substitution Failure Is Not An Error

L’√©chec de substitution n’est pas une erreur (Substitution Failure Is Not An Error, ou SFINAE parce que c’est un nom beaucoup trop long) est une r√®gle qui s’applique pendant la r√©solution d’une fonction templat√©e qui poss√®de plusieurs surcharges.

Elle signifie que si la r√©solution du type (d√©duit ou sp√©cifi√©) du template d’un param√®tre √©choue, alors la sp√©cialisation est mise de c√īt√© sans g√©n√©rer d’erreur (et on continue d’essayer de r√©soudre le template normalement).

Par exemple, prenons le code suivant :

struct Foo {};
struct Bar { Bar(Foo){} }; // Bar can be created from Foo
  
template <class T>
auto f(T a, T b) -> decltype(a+b); // 1st overload
  
Foo f(Bar, Bar);  // 2nd overload
  
Foo a, b;
Foo x3 = f(a, b);

Instinctivement, on pourrait se dire que c’est la premi√®re surcharge qui est appel√©e ligne 10 (parce que l’instanciation qui utilise Foo en tant que T est une surcharge plus ad√©quate que l’autre, qui n√©cessite une conversion).

Cependant, l’expression (a+b) n’est pas solvable pour le type Foo. Mais √† la place de g√©n√©rer une erreur de compilation (du type ¬ę¬†pas d’op√©rateur ‘+’ n’a √©t√© trouv√© pour Foo¬†¬Ľ) cette surcharge est mise de c√īt√©. Il ne reste plus que l’autre surcharge, qui fonctionne parce qu’on peut convertir implicitement un Foo en Bar.

Ce genre de substitution se produit pour tous les types utilis√©s dans les types de fonction et pour tous les types utilis√©s dans les d√©clarations de param√®tres templat√©s. Depuis le C++11, cela se produit √©galement pour toutes les expressions utilis√©es dans le type d’une fonction et toutes les expressions utilis√©es dans les d√©clarations de param√®tres templat√©s. Depuis le C++20, cela se produit aussi pour toutes les expressions utilis√©es dans les sp√©cifieurs explicites.

La documentation compl√®te du SFINAE se trouve l√† : SFINAE ‚Äď cppreference.com.

Autres features en C++20

Les templates continuent toujours d’√©voluer aujourd’hui. Voici une petite liste des fonctionnalit√©s concernant les templates qui sont apparue en C++20 et auxquelles je n’ai pas pu faire une place dans cet article :

  • Les listes de param√®tres templat√©s pour les lambdas g√©n√©riques. Parfois les lambdas g√©n√©riques sont trop g√©n√©riques. Le C++20 permet d’utiliser la syntaxe famili√®re de fonctions templat√©es pour introduire directement des noms de type.
  • La d√©duction d’argument de template de classe pour les agr√©gats. En C++17 on avait besoin d’expliciter des guides de d√©duction pour la d√©duction d’argument de template avec les agr√©gats. Plus maintenant.
  • Les classes dans des param√®tres de template non-typ√©s. Les param√®tres de template non-typ√©s peuvent maintenant √™tre des classes litt√©rales.
  • Les param√®tres des template non-typ√© g√©n√©ralis√©s. Les param√®tres des template non-typ√© sont g√©n√©ralis√©s aux soi-disants type structuraux.

Exceptions et templates : les deux revers d’une m√™me m√©daille

Je n’ai pas parl√© d’exceptions dans cet article, mais pour Stroustrup, les exceptions et les templates sont des fonctionnalit√©s compl√©mentaires :

Dans ma t√™te, les templates et les exceptions sont deux revers d’une m√™me m√©daille : les templates permettent de r√©duire le nombre d’erreurs d’ex√©cution en √©tendant le panel de probl√®mes que la v√©rification statique de type peut r√©soudre ; les exceptions fournissent un m√©canisme pour traiter les erreurs d’ex√©cution restantes. Les templates font que le traitement des exceptions est faisable en r√©duisant le besoin de g√©rer des erreurs √† l’ex√©cution, ne laissant que les cas essentiels. Les exceptions font que les librairies g√©n√©rales bas√©es sur des templates sont g√©rables en donnant √† ces librairies une mani√®re de remonter des erreurs.
‚ÄĒ Bjarne Stroustrup, The Design And Evolution Of C++, Chapitre 15 : Templates, ¬ß1 – Introduction

Donc, par construction, les templates et les exceptions sont li√©es, en plus d’√©lever le niveau d’abstraction du code o√Ļ elles sont employ√©es.

Cependant, les exceptions et les templates (surtout les templates) ont √©volu√© depuis, donc je pense que cela n’est plus trop vrai aujourd’hui.

Conclusion

D’apr√®s moi, les templates sont le plus gros poisson dans le lac m√©taphorique qu’est le C++. On n’en parlera jamais assez, et je pense qu’ils continueront d’√©voluer pour des d√©c√©nnies encore.

Il en est ainsi car, dans le C++ moderne, une des id√©es-cl√©s est qu’on souhaite √©crire des intentions plut√īt que des actions. Nous voulons un niveau d’abstraction toujours plus √©lev√© et plus de m√©taprogrammation. Ainsi, il est normal que les templates soient au coeur des √©volutions actuelles.

Merci de votre attention et à la semaine prochaine !

Article original : [History of C++] Templates: from C-style macros to concepts | Belay the C++ (belaycpp.com)
Traductrice : Chloé Lourseyre

Addenda

Notes


1 – J’ai r√©ussi √† isoler le changement dans le compilateur de GCC √† la version 6 ([https://godbolt.org/z/vndGdd7Wh][11]), sugg√©rant que √ßa a effectivement √©t√© pris en compte lors du passage au C++14. J’ai r√©ussi √† observer la m√™me chose aupr√®s de clang, √† la version 6 ([https://godbolt.org/z/ssfxvb4cM][12]), ce qui confirme cette hypoth√®se.

2 – On appelle √ßa le duck-typing (le typage-canard). Si √ßa ressemble √† un canard, que √ßa nage comme un canard et que √ßa cancane comme un canard, alors c’est probablement un canard.

3 – Je n’ai cependant pas d’exemple concret a pr√©senter pour illustrer cette assertion et je ne fais que plus ou moins paraphraser Stroustrup sur le sujet. Cependant, l’id√©e d’avoir des contraintes impl√©ment√©es par l’utilisateur ferme des portes dont vous ne saviez pas qu’elles existaient et qui pourraient tout √† fait √™tre exploit√©es.

4 – Il y a eu d’autres essais pour exprimer des contraintes, mais sans succ√®s. Vous pouvez avoir plus de d√©tails sur ces essais au paragraphe ¬ß15.4 de Stroustrup: The Design and Evolution of C++.

5 – Ces instanciations sont (d’apr√®s le code assembleur ‚Äď Compiler Explorer (godbolt.org)):
– log(int);
– log(char[4]);
– log(char);
– log(double);
– log(int,int,int,char[4]);
– log(int,int,char[4]);
– log(int,char[4]);
– log(char,double);

Sources

Par ordre d’apparition :

Laisser un commentaire