23 nov. 2006

Etude du C++ Technical Report 1 - reference_wrapper : errata 1

Le billet précédent a reçu sur ma propre échelle de satisfaction une terrible note, donnée par moi même après une revue plus sérieuse du code produit. De nombreux oublis émaillent ce texte, et le code source correspondant ne correspond pas du tout aux exigences du TR1. Avant toute chose, revoyons ensemble le texte de mon commentaire.

Il était évident que mon implémentation de std::tr1::reference_wrapper<> allait être truffé d'erreurs et d'omissions. Fort heureusement, je m'en suis rendu compte trop tard, et je vous ait donc livré un code comportant un certain nombre de défauts, dont voici la liste :

1) les fonctions std::tr1::ref() et std::tr1::cref() ne sont pas définies. Cette erreur est simple à corriger.
2) plus ennuyeux, reference_wrapper<>::result_type n'est pas traité correctement. En effet, result_type devrait être défini à partir du moment où le type encapsulé est un pointeur sur fonction ou un pointeur sur une fonction membre. Or, dès lors que l'on définit un reference_wrapper<> autour d'une de ces deux entités de manière à ne pas tomber dans les cas "normaux" (une ou deux paramètres avec un type de retour), result_type n'est pas défini.
3) encore plus ennuyeux, lorsque le type encapsulé dérive à la fois de std::unary_function<> et std::binary_function<> et que les result_type de ces deux classes sont différents, reference_wrapper<>::result_type n'est pas défini correctement. Cela se complique notamment lorsqu'il faut prendre en compte le fait que la classe encapsulée peut très bien redéfinir result_type - dans ce cas, c'est cette définition qu'il faut utiliser. A l'heure actuelle, je ne vois pas encore de moyen de détecter ce cas...
4) et pour finit, terriblement ennuyeux : l'invocation des fonctions encapsulées (soit par le biais d'un pointeur de fonction, d'un pointeur sur une fonction membre où d'un functor) n'est pas implémenté du tout.

Comme vous le voyez, la liste des défauts est loin d'être minime.

J'espère toutefois vous fournir une version partiellement (voire totalement corrigée) rapidement - hélas, je n'ai pas la possibilité de vous indiquer dès maintenant une date fixe. Certain de ces défauts ne sont en effet pas des plus simples à corriger (en particulier le problème de l'invocation).

Comme de bien entendu, je n'ai pas résolu l'ensemble de ces problèmes - ainsi, mon implémentation du point (3) est somme toute litigieuse (mais je continue mes recherches pour trouver une solution correcte). Le point (1) a été le premier corrigé. Une nouvelle implémentation du point (3) a été développée, mais comme je viens de le dire cette implémentation ne corrige que partiellement le problème.

Le problème avec le point (3) tiens à Visual C++ .NET 2005 Express Edition (mais le compilateur étant le même que sur les autres plateformes VC++.NET 2005, le problème peut être reproduit aisément). Considérons le code suivant :

template <class __type>
class has_result_type
{
  typedef char __one[1];
  typedef char __two[2];
  template <class wrapped> struct wrapper { };
  template <class u> static __one& test(wrapper<typename u::result_type>*);
  template <class u> static __two& test(...);
public:
   static const bool value = (sizeof(test<__type>(0)) == 1);
};
struct a { typedef float result_type; }; struct b { typedef int result_type; }; struct c : public a, public b { }; struct d : public a, public b { typedef bool result_type; };
int main() { std::cout << "a: " << has_result_type<a>::value; std::cout << "; b: " << has_result_type<b>::value; std::cout << "; c: " << has_result_type<c>::value; std::cout << "; d: " << has_result_type<d>::value << std::endl; return 0; }

Si ce code est compilé avec g++, le résultat qui sera écrit sur la sortie standard est a: 1; b: 1; c: 0; d: 1[1]. Quand à lui, VC++.NET 2005 va compiler le code de manière à produire a: 1; b: 1; c: 1; d: 1. De fait, je ne sais plus différencier le cas de la structure c (qui ne définit pas result_type) de celui de la structure d (qui définit result_type). Intuitivement, on sent que g++ a raison sur ce point particulier[2].

Devant l'impossibilité (je l'espère, momentanée) de gérer ce cas particulier, j'ai pris la liberté de définir result_type de la manière suivante pour le cas où __type dérive de unary_function et binary_function :

  • si ces deux classes ont le même result_type, reference_wrapper<T>::result_type sera un typedef vers ce type.
  • si le result_type de ces classes est différent, reference_wrapper<T>::result_type ne sera pas défini.

Ce n'est évidement pas le comportement correct - mais je ne sais pas encore comment l'implémenter sur VC++.NET 2005, il faudra donc faire avec pendant un temps.

Les deux autres points ((2) et (4)) sont en cours d'implémentation. L'invocation (point (4)) nécessite l'implémentation de tr1::result_of, dont nous reparlerons plus tard. Le point (2) se sert quand à lui des traits de type (type traits) qui ferons eux-aussi l'objet d'une étude ultérieure.

Dans l'ensemble, ne vous étonnez pas si cette implémentation est à la fois incorrecte et incomplète. Après tout, le développement est une entreprise itérative... (comment-ça, "c'est une mauvaise excuse...")

Notes

[1] je n'ai pas fait le test moi même. Victor Nicolet l'a effectué - en utilisant les options -ansi -pedantic

[2] reste à savoir lequel de ces deux compilateur respecte la norme C++ dans ce cas... si l'un d'eux la respecte, bien sûr. La réponse dans un prochain billet.

Ajouter un commentaire

Les commentaires peuvent être formatés en utilisant une syntaxe wiki simplifiée.

Fil des commentaires de ce billet