Bonjour à tous. Voici un petit texte issue d'une reflexion personnelle. Merci de me dire ce que vous en pensez 
Comme beaucoup d'entre vous le savent probablement, la prochaine révision du C++ devrait ajouter au langage la notion de concepts, laquelle consite à définir une interface pour chaque type afin de pouvoir vérifier au moment de la compilation si des paramètres templates possèdent bien toutes les fonctions nécessaires, et le cas échéans devrait permettre de générer des erreurs plus explicite que ce que l'on a actuellement.
Au départ, j'ai trouvé cet ajout au langage très intéressant. Cependant, je me suis rendu compte récemment (et un peu par hazard) que même si ce principe de vérification était très utile, il n'est pas forcément nécessaire d'ajouter de nouveaux mots-clés et cette notion de concepts, car en fait, il est (sauf erreur de ma part) tout à fait possible d'avoir le même résulat en utilisant les templates tels qu'ils existent actuellement, et en garadant à peu de chose près la même syntaxe que celle introduite par les concepts. En voici le principe:
L'idée de base des concepts est de créer à l'aide du mot-cle concept une sorte d'interface à laquelle doit satisfaire un type donné.
exemple (inspiré d'un exemple du pdf suivant):
1 2 3 4 5 6 7 8
|
concept InputIterator<typename Iter, typename Value>{
Iter& operator=(Iter&, const Iter& iter);
Value operator*(const Iteré iter);
Iter& operator++(Iter& iter);
bool operator==(const Iter&, const Iter&);
//...
} |
Ensuite, on déclare une classe template en spécifiant un concept plutot que le mot cle typename. Le concept est alors "instancié", et vérifie que le type passé en paramètre est bien conforme à ce qui est attendu au moment de son instanciation.
1 2 3 4 5 6 7 8 9
|
template <InputIterator iterator>
class A
{
//...
iterator& begin();
iterator& end();
//...
} |
Maintenant, réécrivons la chose en n'utilisant que des templates. Tout d'abord il faut déclarer une classe qui va émuler la déclaration du concept précédent:
1 2 3 4 5 6 7 8 9
|
template<typename T>
class InputIterator
{
Iter& operator=(Iter& thiz, const Iter& iter){return thiz=iter;}
Value operator*(const Iter& iter){return *iter;}
bool operator++(const Iter& iter){return iter++;}
bool operator==(const Iter& thiz, const Iter& iter){return thiz==iter;}
} |
Ensuite il faut vérifier lors de l'instaciation de la classe que le template est bien conforme à nos exigence. Certes, on ne peut le spécifier dans la déclaration du template lui-même car il faut utiliser le mot-cle typename, par contre on peut toujours forcer l'instanciation de notre concept dans la déclaration de la classe
... comme ceci:
1 2 3 4 5 6 7 8 9 10
|
template <typename T>
class A
{
typedef InputIterator<T> iterator;
//...
iterator& begin();
iterator& end();
//...
} |
Et voilà! La ligne
typedef InputIterator<T> iterator;
à elle seule va alors se charger d'instancier la classe InputIterator, laquelle ne sert qu'à vérifier que le type passé en pramètre contient bien toutes les fonctions dont on a besoin, juste comme le fairait l'utilisation d'un concept.
A noter au passage que certains diront probablement que l'on a un appel supplémentaire qui se fait, et que cela engendre un surcout en terme de performance. A cela je répondrai que je fais confiance à mon compilateur pour éviter ce genre de désagrément et remplacer un appel à InputIterator<T>::operator++ par un appel à T::operator++ par exemple, et ce de façon automatique. Et puis, l'implémentation que je donne de la classe InputIterator n'est qu'un exemple. Mais à priori, il doit également être possible de la définir comme étant une classe qui hérite de T, et ensuite d'utiliser la directive using afin de tester la présence de chaque opérateur plutot que de les définir à chaque fois. Cela donne alors quelque chose du style:
1 2 3 4 5 6 7 8 9
|
template<typename T>
class InputIterator : public T
{
using T::operator=;
using t::operator*;
using t::operator++;
using t::operator==;
} |
Pour info, je viens de tester cette seconde implémentation pour voir si cela était effectivement possible, et après avoir tenter d'instancier un InputIterator<string>, j' obtenu l'erreur suivante:
1 2 3 4
| tests_gcc.cpp: In instantiation of 'InputIterator<std::basic_string<char, std::char_traits<char>, std::allocator<char> >, char>':
tests_gcc.cpp:23: instantiated from here
tests_gcc.cpp:11: erreur: no members matching 'std::basic_string<char, std::char_traits<char>, std::allocator<char> >::operator==' in 'struct std::basic_string<char, std::char_traits<char>, std::allocator<char> >'
tests_gcc.cpp:12: erreur: no members matching 'std::basic_string<char, std::char_traits<char>, std::allocator<char> >::operator++' in 'struct std::basic_string<char, std::char_traits<char>, std::allocator<char> >' |

En plus, cette approche devrait aussi permettre de tester la présence de constructeurs par défaut et/ou de destructeur. (a vérifier pour le destructeur, bien que l'intéret d'avoir un destructeur est moins évidente je trouve).
On peut même aller plus loin dans ce raisonnement, car les concepts ne se limitent pas à cette déclaration mais permettent aussi d'adapter le cas en permettant de "forcer" la conformance à un concept pour un type donné. Cela ressemble en fait à ce que l'on pourrer appeler de la spécialisation de concept. Inutile de dire que ce comportement peut facilement être émuler via la spécialisation de template. De même, on peut utiliser des notions telles que l'héritage de classe pour créer une hiérarchie de concepts, qu'ils soient implémentés via la notion de concept que tend à introduire la norme C0x ou via l'utilisation de classes templates.
Enfin, je dirai que cette approche apporte pour moi un autre avantage que celui de ne pas nécessiter la modification du langage en lui-même. En effet, via cette approche il est facile de vérifier qu'un type reçu en paramètre vérifie non pas une mais différentes interfaces. Il suffit pour cela d'ajouter autant de typedefs qu'il n'y a de vérifiactions à effectuer. En fait, il la seule chose pour laquelle je n'ai pas de solution actuellement est ce qui est appelé le "concept-based overloading", à savoir la possibilité de spécialiser une classe template en fonction de l'interface à laquelle satisfait un type donné. Mais en cherchant, il doit bien avoir moyen d'y arriver.
Partager