Implémentation complète de lexical_cast

Le code de cet exemple vient en deux versions (deux « saveurs »), soit C++ 03 et C++ 11. Avec la version C++ 03, certains traits sont implémentés manuellement, alors qu'ils sont offerts par le standard depuis C++ 11. Bien entendu, préférez C++ 17 si votre compilateur le supporte.

Version C++ 17

Ceci est une implémentation complète (dans la mesure de ce que j'ai choisi de faire; on pourrait aller plus loin et considérer les cas où const char* et const wchar_t* servent de type source pour les conversions texte à texte) de lexical_cast (dont nous avons discuté dans l'article disponible ici).

En bref, cette implémentation ne repose que sur des bibliothèques standards (le code devrait être pleinement portable, modulo des bogues qui auraient pu m'échapper... ou d'occasionnelles bogues de bibliothèque standard, ce qui se produit à l'occasion).

#include <type_traits>
#include <string>
#include <string_view>
#include <utility>
#include <sstream>
#include <iterator>

J'ai conservé les types étiquettes et les fonctions spécialisées sur cette base, pour réduire les disparités avec les versions antérieures, mais on aurait pu s'y prendre autrement cette fois.

class implicite   {};
class texte_dest  {};
class texte_texte {};
class general     {};

Le trait est_texte<T> s'avèrera pour tout type T de la forme basic_string<C> pour un type C donné. Je l'ai aussi enrichi de support pour basic_string_view<C>, ce qui demande toutefois un autre ajustement...

template <class>
   struct est_texte : std::false_type {
   };
template <class C>
   struct est_texte<std::basic_string<C>> : std::true_type {
   };
template <class C>
   struct est_texte<std::basic_string_view<C>> : std::true_type {
   };
template <class T>
   constexpr auto est_texte_v = est_texte<T>::value;

... car si le type basic_string_view<C> est excellent à titre de type de paramètre pour des chaînes de caractères non-modifiables, il est beaucoup moins à propos à titre de type de retour.

Ainsi, j'utiliserai unview_t<T> pour transformer les types de retour qui auraient été de la forme basic_string_view<C> en basic_string<C> quand je retournerai des valeurs des diverses implémentations de lexical_cast.

 template <class T>
   struct unview {
      using type = T;
   };
template <class C>
   struct unview<std::basic_string_view<C>> {
      using type = std::basic_string<C>;
   };
template <class T>
   using unview_t = typename unview<T>::type;

Les diverses implémentations de lexical_cast suivent. Notez que si le contexte dans lequel vous travaillez ne vous permet pas de signaler une erreur de conversion par la voie d'une levée d'exception, il existe d'autres stratagèmes pour signaler le problème.

class bad_lexical_cast {};

template <class D, class S>
   unview_t<D> lexical_cast(const S &src, general) {
      stringstream sstr;
      sstr << src;
      if(D dest; sstr >> dest)
         return dest;
      throw bad_lexical_cast{};
   }
template <class D, class S>
   unview_t<D> lexical_cast(const S &src, texte_dest) {
      using value_type = typename D::value_type;
      std::basic_stringstream<value_type> sstr;
      sstr << src;
      return sstr.str();
   }
template <class D, class S>
   unview_t<D> lexical_cast(const S &src, texte_texte) {
      return { std::begin(src), std::end(src) };
   }
template <class D, class S>
   unview_t<D> lexical_cast(const S &src, implicite) {
      return src;
   }

La fonction lexical_cast appelée par le code client réalise ici son aiguillage par if constexpr, ce qui constitue un gain net de simplicité et d'efficacité en comparaison avec les approches antérieures.

template <class D, class S>
   auto lexical_cast(S &&src) {
      if constexpr(std::is_same_v<D,S> || std::is_convertible_v<S,D>)
         return lexical_cast<D, S>(std::forward<S>(src), implicite{});
      else
         if constexpr(est_texte_v<D>)
            if constexpr(est_texte_v<S>)
               return lexical_cast<D, S>(std::forward<S>(src), texte_texte{});
            else
               return lexical_cast<D, S>(std::forward<S>(src), texte_dest{});
         else
            return lexical_cast<D, S>(std::forward<S>(src), general{});
   }

Le reste n'est qu'un petit exemple de code de test.

#include <iostream> 
using namespace std; 

class X {};
istream& operator>>(istream &is, X&) {
   return is;
}
ostream& operator<<(ostream &os, const X&) {
   return os;
}
 
struct Y {
   Y() = default;
   Y(const X&) {}
};
istream& operator>>(istream &is, Y&) {
   return is;
}
ostream& operator<<(ostream &os, const Y&) {
   return os;
}
 
int main() {
   string s = "3";
   auto i = lexical_cast<int>(s); // sérialisation par flux
   s = lexical_cast<string>(i);  // sérialisation par flux, texte en sortie
   auto ws = lexical_cast<wstring>(i);  // idem
   s = lexical_cast<string>(s);  // implicite
   ws = lexical_cast<wstring>(s); // texte à texte
   s = lexical_cast<string>(ws); // idem
   X x;
   Y y;
   y = lexical_cast<Y>(x); // implicite
   x = lexical_cast<X>(y); // via flux
}

Version C++ 11

Ceci est une implémentation complète (dans la mesure de ce que j'ai choisi de faire; on pourrait aller plus loin et considérer les cas où const char* et const wchar_t* servent de type source pour les conversions texte à texte) de lexical_cast (dont nous avons discuté dans l'article disponible ici).

#include <type_traits>
#include <string>
#include <sstream>
#include <algorithm>
#include <iostream>

class implicite   {};
class texte_dest  {};
class texte_texte {};
class general     {};

template <class>
   struct est_texte : std::false_type {
   };
template <class C>
   struct est_texte<std::basic_string<C>> : std::true_type {
   };
 
template <class D, class S>
   struct traits_conversion {
      using type = std::conditional_t<
         std::is_same<D,S>::value || std::is_convertible<S, D>::value,
         implicite,
         std::conditional_t<
            est_texte<D>::value,
            std::conditional_t<
               est_texte<S>::value,
               texte_texte,
               texte_dest
            >,
            general
         >
      >;
   };
 
template <class D, class S>
   D lexical_cast(const S &src, general) {
      stringstream sstr;
      sstr << src;
      D dest;
      sstr >> dest; // on suppose une conversion sans erreur; ajuster au besoin
      return dest;
   }
template <class D, class S>
   D lexical_cast(const S &src, texte_dest) {
      using value_type = typename D::value_type;
      basic_stringstream<value_type> sstr;
      sstr << src;
      return sstr.str();
   }
template <class D, class S>
   D lexical_cast(const S &src, texte_texte) {
      return { begin(src), end(src) };
   }
template <class D, class S>
   D lexical_cast(const S &src, implicite) {
      return src;
   }
 
//#include <iostream>
//template <class T, class U>
//   void diag() {
//      using namespace std;
//      cout << typeid(T).name() << " <-- \n\t" << typeid(U).name() << endl;
//      cout << "\tMeme type? " << is_same<T,U>::value << endl;
//      cout << "\t" << typeid(traits_conversion<T, U>::type()).name() << endl;
//   }
 
template <class D, class S>
   D lexical_cast(S &&src) {
      //diag<D, S>();
      return lexical_cast<D, S>(std::forward<S>(src), typename traits_conversion<D, S>::type{});
   }
#include <iostream> 
using namespace std; 

class X {};
istream& operator>>(istream &is, X&) {
   return is;
}
ostream& operator<<(ostream &os, const X&) {
   return os;
}
 
struct Y {
   Y() = default;
   Y(const X&) {}
};
istream& operator>>(istream &is, Y&) {
   return is;
}
ostream& operator<<(ostream &os, const Y&) {
   return os;
}
 
int main() {
   string s = "3";
   auto i = lexical_cast<int>(s); // sérialisation par flux
   s = lexical_cast<string>(i);  // sérialisation par flux, texte en sortie
   auto ws = lexical_cast<wstring>(i);  // idem
   s = lexical_cast<string>(s);  // implicite
   ws = lexical_cast<wstring>(s); // texte à texte
   s = lexical_cast<string>(ws); // idem
   X x;
   Y y;
   y = lexical_cast<Y>(x); // implicite
   x = lexical_cast<X>(y); // via flux
}

Version C++ 03

Pour en savoir plus sur quelques outils auxiliaires utilisés ci-dessous, voir :

Ceci est une implémentation complète (dans la mesure de ce que j'ai choisi de faire; on pourrait aller plus loin et considérer les cas où const char* et const wchar_t* servent de type source pour les conversions texte à texte) de lexical_cast (dont nous avons discuté dans l'article disponible ici).

#include <string>
#include <sstream>
#include <iostream>
using namespace std;

class implicite   {};
class texte_dest  {};
class texte_texte {};
class general     {};

template <bool, class, class>
   struct static_if_else;
template <class SiVrai, class SiFaux>
   struct static_if_else<true, SiVrai, SiFaux> {
      typedef SiVrai type;
   };
template <class SiVrai, class SiFaux>
   struct static_if_else<false, SiVrai, SiFaux> {
      typedef SiFaux type;
   };

template <class T, class U>
   struct meme_type {
      enum { value = false };
   };
template <class T>
   struct meme_type<T,T> {
      enum { value = true };
   };

template <class S, class D>
   class est_convertible {
      typedef char oui_type;
      struct non_type { char _[3]; } ;
      static oui_type test(D);
      static non_type test(...);
      static S creer();
   public:
      enum { value = sizeof(test(creer()))==sizeof(oui_type) };
   };

template <class>
   struct est_texte {
      enum { value = false };
   };
template <class C>
   struct est_texte<basic_string<C> > {
      enum { value = true };
   };

template <class D, class S>
   struct traits_conversion {
      typedef typename static_if_else<
         meme_type<D,S>::value || est_convertible<S, D>::value,
         implicite,
         typename static_if_else<
            est_texte<D>::value,
            typename static_if_else<
               est_texte<S>::value,
               texte_texte,
               texte_dest
            >::type,
            general
         >::type
      >::type type;
   };


template <class D, class S>
   D lexical_cast(const S &src, general) {
      stringstream sstr;
      sstr << src;
      D dest;
      sstr >> dest;
      return dest;
   }
template <class D, class S>
   D lexical_cast(const S &src, texte_dest) {
      typedef typename
         D::value_type value_type;
      basic_stringstream<value_type> sstr;
      sstr << src;
      return sstr.str();
   }
template <class D, class S>
   D lexical_cast(const S &src, texte_texte) {
      return D(src.begin(), src.end());
   }
template <class D, class S>
   D lexical_cast(const S &src, implicite) {
      return src;
   }

//template <class T, class U>
//   void diag() {
//      cout << typeid(T).name() << " <-- \n\t" << typeid(U).name() << endl;
//      cout << "\tMeme type? " << meme_type<T,U>::value << endl;
//      cout << "\t" << typeid(traits_conversion<T, U>::type()).name() << endl;
//   }

template <class D, class S>
   D lexical_cast(const S &src) {
      //diag<D, S>();
      return lexical_cast<D, S>(src, typename traits_conversion<D, S>::type());
   }

class X {};
istream& operator>>(istream &is, X&) {
   return is;
}
ostream& operator<<(ostream &os, const X&) {
   return os;
}

struct Y {
   Y() { }
   Y(const X&) {}
};
istream& operator>>(istream& is, Y&) {
   return is;
}
ostream& operator<<(ostream&os, const Y&) {
   return os;
}

int main() {
   string s = "3";
   int i = lexical_cast<int>(s); // sérialisation par flux
   s = lexical_cast<string>(i);  // sérialisation par flux, texte en sortie
   s = lexical_cast<string>(s);  // implicite
   wstring ws = lexical_cast<wstring>(s); // texte à texte
   X x;
   Y y;
   y = lexical_cast<Y>(x); // implicite
   x = lexical_cast<X>(y); // via flux
}

Valid XHTML 1.0 Transitional

CSS Valide !