web-dev-qa-db-fra.com

Créer une classe définie par l'utilisateur std :: to_string (capable)

Je sais qu'il semble trop Java ou C #. Cependant, est-il possible/bon/sage de rendre ma propre classe valide en tant qu’entrée pour la fonction std::to_string? Exemple:

class my_class{
public:
std::string give_me_a_string_of_you() const{
    return "I am " + std::to_string(i);
}
int i;
};

void main(){
    my_class my_object;
    std::cout<< std::to_string(my_object);
}

S'il n'y a pas une telle chose (et je pense que ça), quelle est la meilleure façon de le faire?

25
Humam Helfawi

Premièrement, quelques ADL aidant:

namespace notstd {
  namespace adl_helper {
    using std::to_string;

    template<class T>
    std::string as_string( T&& t ) {
      return to_string( std::forward<T>(t) );
    }
  }
  template<class T>
  std::string to_string( T&& t ) {
    return adl_helper::as_string(std::forward<T>(t));
  }
}

notstd::to_string(blah) effectuera une recherche ADL de to_string(blah) avec std::to_string dans la portée.

Nous modifions ensuite votre classe:

class my_class{
public:
  friend std::string to_string(my_class const& self) const{
    return "I am " + notstd::to_string(self.i);
  }
  int i;
};

et maintenant notstd::to_string(my_object) trouve le to_string correct, de même que notstd::to_string(7).

Avec un peu plus de travail, nous pouvons même prendre en charge les méthodes .tostring() sur les types à détecter automatiquement et à utiliser.

17

Quelle est la "meilleure" façon est une question ouverte.

Il y a plusieurs façons.

La première chose à dire est que surcharger std::to_string pour un type personnalisé est non autorisé . Nous ne pouvons que spécialiser les fonctions et les classes de modèle dans l'espace de noms std pour les types personnalisés, et std::to_string n'est pas une fonction de modèle.

Cela dit, un bon moyen de traiter to_string ressemble beaucoup à un opérateur ou à une implémentation de swap. c'est-à-dire permettre la recherche dépendante des arguments pour effectuer le travail.

ainsi, lorsque nous voulons convertir quelque chose en chaîne, nous pourrions écrire:

using std::to_string;
auto s = to_string(x) + " : " + to_string(i);

en supposant que x était un objet de type X dans l'espace de noms Y et que i était un int, nous pourrions alors définir:

namespace Y {

  std::string to_string(const X& x);

}

ce qui voudrait dire maintenant que:

invoquer to_string(x) sélectionne en fait Y::to_string(const Y::X&), et

invoquer to_string(i) sélectionne std::to_string(int) 

Pour aller plus loin, il se peut que vous souhaitiez que la chaîne fasse la même chose que l'opérateur <<, afin que l'une puisse être écrite en termes de l'autre:

namespace Y {

  inline std::ostream& operator<<(std::ostream& os, const X& x) { /* implement here */; return os; }

  inline std::string to_string(const X& x) {
    std::ostringstream ss;
    ss << x;
    return ss.str();
  }
}
19
Richard Hodges

Voici une solution alternative. Rien n’est nécessairement plus élégant ou trop sophistiqué, mais c’est une approche alternative. Cela suppose que toutes les classes sur lesquelles vous avez l'intention d'appeler to_string ont une fonction ToString ().

Voici un modèle de fonction qui ne fonctionnera qu'avec des objets de type classe et appellera une fonction ToString ().

    template<typename T, typename = std::enable_if_t<std::is_class<T>::value>>
    std::string to_string(const T& t) {
      return t.ToString();
    }

Nous souhaitons peut-être que cela fonctionne également avec std :: string.

    template<>
    std::string to_string(const std::string& t) {
      return t;
    }

Voici un exemple du code utilisé. Notez l'espace de nom factice to_s. J'imagine que si vous utilisez std :: to_string dans la fonction principale, le nom de notre fonction de modèle est indiqué, nous devons donc introduire le nom indirectement de cette manière. Si quelqu'un sait comment s'y prendre, j'apprécierais un commentaire.

    #include <cstring>
    #include <iostream>
    #include <string>
    #include <type_traits>


    union U {
      double d;
      const char* cp;
    };

    struct A {
      enum State { kString, kDouble };
      State state;
      U u;

      void Set(const char* cp) {
        u.cp = cp;
        state = kString;
      }

      std::string ToString() const {
        switch (state) {
          case A::kString : return std::string(u.cp); break;
          case A::kDouble : return std::to_string(u.d); break;
          default : return "Invalid State";
        }
      }
    };

    namespace to_s { using std::to_string; };

    int main() {
      using namespace to_s;
      std::string str = "a Nice string";
      double d = 1.1;
      A a { A::kDouble, {1.2} };

      std::cout << "str: " << to_string(str) << ", d: " << to_string(d) << std::endl;
      std::cout << "a: " << to_string(a) << std::endl;
      a.Set(str.c_str());
      std::cout << "a: " << to_string(a) << std::endl;
      std::memset(&a, 'i', sizeof(a));
      std::cout << "a: " << to_string(a) << std::endl;
    }

Voici ce que j'ai eu:

str: une belle chaîne, d: 1.100000

a: 1.200000

a: une belle corde

a: Etat invalide

1
Nathan Chappell

Vous pouvez définir votre propre to_string dans son propre espace de nom (par exemple, foo).

namespace foo {
   std::string to_string(my_class const &obj) {
     return obj.string give_me_a_string_of_you();
   }
}

Et utilisez-le comme:

int main(){
    my_class my_object;
    std::cout<< foo::to_string(my_object);
}

Malheureusement, vous ne pouvez pas définir votre propre version de to_string dans l'espace de noms std parce que vous vous conformez au standard 17.6.4.2.1 Espace de noms std [namespace.std]} _ (Emphasis Mine):

Le comportement d'un programme C++ n'est pas défini s'il ajoute des déclarations ou définitions à namespace std ou à un namespace dans namespace std sauf indication contraire. Un programme peut ajouter un modèle spécialisation pour tout modèle de bibliothèque standard à namespace std uniquement si la déclaration dépend d'un type défini par l'utilisateur et du la spécialisation répond aux exigences standard de la bibliothèque pour le modèle d'origine et n'est pas explicitement interdit.

1
101010

Cela a déjà une bonne réponse, mais j'aimerais proposer une alternative, les commentaires sont les bienvenus.

Si vous n'êtes pas défini sur le nom de la fonction to_string, vous pouvez implémenter votre propre modèle de fonction libre ToString, avec des spécialisations pour les types pris en charge par to_string:

template<class T>
std::string ToString(const T& t)
{
    std::ostringstream stream;
    const uint8_t* pointer = &t;
    for(size_t i=0;i<sizeof(T);++i)
    {
        stream << "0x" << std::hex << pointer[i];
    }
    return stream.str();
}

template<> std::string ToString(const int& t) { return std::to_string(t); }
template<> std::string ToString(const long& t) { return std::to_string(t); }
template<> std::string ToString(const long long& t) { return std::to_string(t); }
template<> std::string ToString(const unsigned& t) { return std::to_string(t); }
template<> std::string ToString(const unsigned long& t) { return std::to_string(t); }
template<> std::string ToString(const unsigned long long& t) { return std::to_string(t); }
template<> std::string ToString(const float& t) { return std::to_string(t); }
template<> std::string ToString(const double& t) { return std::to_string(t); }

L'implémentation par défaut ici renvoie une chaîne de valeurs hexadécimales avec les valeurs de l'espace mémoire pour la référence de classe transmise, alors que les spécialisations appellent std :: to_string, cela rendra toute classe "stringable".

Ensuite, il vous suffit de mettre en œuvre votre propre spécialisation pour votre classe:

template<> std::string ToString(const my_class& t) { return "I am " + std::to_string(t.i); }
1
Rodrigo Hernandez

Vous voulez probablement juste surcharger operator<<() quelque chose comme:

std::ostream& operator << ( std::ostream& os, const my_class& rhs ) {
    os << "I am " << rhs.i;
    return os;
}

Alternativement:

std::ostream& operator << ( std::ostream& os, const my_class& rhs ) {
    os << rhs.print_a_string();
    return os;
}

Ensuite, vous pouvez simplement faire:

int main() {
    my_class my_object;
    std::cout << my_object;

    return 0;
}
0
Paul Evans

Vous ne pouvez pas ajouter de nouvelles surcharges de to_string dans l'espace de noms std, mais vous pouvez le faire dans votre espace de noms:

namespace my {
   using std::to_string;

   std::string to_string(const my_class& o) {
     return o.give_me_a_string_of_you();
   }
}

Ensuite, vous pouvez utiliser my::to_string pour tous les types.

int main()
{
    my_class my_object;

    std::cout << my::to_string(my_object);
    std::cout << my::to_string(5);
}
0
Stas