Les deux éléments les plus importants qui facilitent la métaprogrammation de modèles dans D sont les contraintes de modèles et static if
- deux éléments que le C++ pourrait théoriquement ajouter et qui lui seraient très utiles.
Les contraintes de modèle vous permettent de mettre une condition sur un modèle qui doit être vraie pour que le modèle puisse être instancié. Par exemple, voici la signature de l'un des modèles suivants std.algorithm.find
Les surcharges de l
R find(alias pred = "a == b", R, E)(R haystack, E needle)
if (isInputRange!R &&
is(typeof(binaryFun!pred(haystack.front, needle)) : bool))
Pour que cette fonction modèle puisse être instanciée, le type R
doit être une plage d'entrée telle que définie par std.range.isInputRange
(donc isInputRange!R
doit être true
), et le prédicat donné doit être une fonction binaire qui se compile avec les arguments donnés et renvoie un type implicitement convertible en bool
. Si le résultat de la condition dans la contrainte du modèle est false
le modèle ne sera pas compilé. Non seulement cela vous protège des vilaines erreurs de gabarit que vous obtenez en C++ lorsque les gabarits ne se compilent pas avec leurs arguments donnés, mais cela vous permet de surcharger les gabarits en fonction de leurs contraintes de gabarit. Par exemple, il existe une autre surcharge de find
qui est
R1 find(alias pred = "a == b", R1, R2)(R1 haystack, R2 needle)
if (isForwardRange!R1 && isForwardRange!R2
&& is(typeof(binaryFun!pred(haystack.front, needle.front)) : bool)
&& !isRandomAccessRange!R1)
Il prend exactement les mêmes arguments, mais sa contrainte est différente. Ainsi, différents types fonctionnent avec différentes surcharges de la même fonction modèle, et la meilleure implémentation de la fonction find
peuvent être utilisés pour chaque type. Il n'y a aucun moyen de faire ce genre de choses proprement en C++. Avec un peu de familiarité avec les fonctions et les modèles utilisés dans une contrainte de modèle typique, les contraintes de modèle en D sont assez faciles à lire, alors qu'il faut une métaprogrammation de modèle très compliquée en C++ pour même tenter quelque chose comme ça, que le programmeur moyen ne sera pas capable de comprendre, et encore moins de faire tout seul. Boost en est un excellent exemple. Il fait des choses incroyables, mais il est incroyablement compliqué.
static if
améliore encore la situation. Comme pour les contraintes de modèle, toute condition pouvant être évaluée au moment de la compilation peut être utilisée avec cette contrainte.
static if(isIntegral!T)
{
//...
}
else static if(isFloatingPoint!T)
{
//...
}
else static if(isSomeString!T)
{
//...
}
else static if(isDynamicArray!T)
{
//...
}
else
{
//...
}
La branche qui est compilée dépend de la condition qui est évaluée en premier lieu à true
. Ainsi, à l'intérieur d'un modèle, vous pouvez spécialiser des parties de son implémentation en fonction des types avec lesquels le modèle a été instancié - ou en fonction de tout autre élément qui peut être évalué au moment de la compilation. Par exemple, core.time
utilise
static if(is(typeof(clock_gettime)))
de compiler le code différemment selon que le système fournit ou non des clock_gettime
ou non (si clock_gettime
est présent, il l'utilise, sinon il utilise gettimeofday
).
L'exemple le plus frappant que j'ai vu où D améliore les modèles est probablement celui d'un problème que mon équipe a rencontré en C++. Nous devions instancier un modèle différemment selon que le type qui lui était donné était dérivé d'une classe de base particulière ou non. Nous avons fini par utiliser une solution basée sur cette question de stack overflow . Cela fonctionne, mais c'est assez compliqué pour tester si un type est dérivé d'un autre.
En revanche, dans D, il suffit d'utiliser la fonction :
opérateur.
auto func(T : U)(T val) {...}
Si T
est implicitement convertible en U
(comme ce serait le cas si T
ont été obtenues à partir de U
), alors func
sera compilé, alors que si T
n'est pas implicitement convertible en U
alors il ne le fera pas. Cela une simple amélioration rend les spécialisations de modèles, même les plus basiques, beaucoup plus puissantes (même sans contraintes de modèles ou de static if
).
Personnellement, j'utilise rarement les modèles en C++, sauf pour les conteneurs et les fonctions occasionnelles en <algorithm>
Il n'est pas possible de les utiliser, car ils sont trop pénibles à mettre en œuvre. Ils donnent lieu à de vilaines erreurs et il est très difficile de faire quelque chose de fantaisiste avec. Pour faire quoi que ce soit d'un peu compliqué, il faut être très doué avec les modèles et la métaprogrammation des modèles. Avec les modèles en D, c'est tellement facile que je les utilise tout le temps. Les erreurs sont beaucoup plus faciles à comprendre et à gérer (bien qu'elles soient toujours pires que les erreurs typiques des fonctions non modélisées), et je n'ai pas besoin de comprendre comment forcer le langage à faire ce que je veux avec une métaprogrammation fantaisiste.
Il n'y a aucune raison pour que le C++ ne puisse pas bénéficier d'une grande partie des capacités de D (les concepts C++ seraient utiles s'ils étaient un jour mis au point), mais jusqu'à ce qu'ils ajoutent une compilation conditionnelle de base avec des constructions similaires aux contraintes de modèles et à l'approche static if
en C++, les modèles C++ ne pourront pas être comparés aux modèles D en termes de facilité d'utilisation et de puissance.
8 votes
S'ils sont tous les deux turing complets, la réponse est rien :)
9 votes
@awoodland : Cela n'est vrai que pour une définition très limitée de "faire". Selon toute définition normale, il y a beaucoup de choses que vous ne pouvez pas faire avec les templates C++ (écrire dans des fichiers par exemple - mais j'imagine que vous ne pouvez pas non plus le faire avec la métaprogrammation de templates en D).
8 votes
@awoodland : Turing tarpit, quelqu'un ? ;)
0 votes
@Paul : Voulez-vous dire C++03 et antérieurs, ou voulez-vous dire C++0x/C++11 ?
4 votes
@Merhdad Le C++11 ajoute définitivement des choses utiles aux templates (comme les templates variadiques) qui font qu'ils ne sont pas aussi dépassés, mais sans une sorte de compilation conditionnelle comme celle de D, ils ne sont toujours pas proches des templates de D. Donc, que vous parliez de C++11 ou de pré-C++11 est certainement pertinent pour la question, mais cela n'a finalement pas beaucoup d'importance.
0 votes
@Mehrdad Le "meilleur" C++, donc je suppose C++11.