it-swarm.com.de

Erstellen einer benutzerdefinierten Klasse std :: to_string (able)

Ich weiß, es scheint zu viel Java oder C #. Ist es möglich/gut/weise, meine eigene Klasse als Eingabe für die Funktion std::to_string? .__ gültig zu machen. Beispiel:

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);
}

Wenn es so etwas nicht gibt (und ich denke das), was ist der beste Weg, dies zu tun?

25
Humam Helfawi

Zunächst einige ADL, die helfen:

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) führt eine ADL-Suche von to_string(blah) mit std::to_string im Gültigkeitsbereich durch.

Wir modifizieren dann Ihre Klasse:

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

und jetzt findet notstd::to_string(my_object) den richtigen to_string, ebenso wie notstd::to_string(7).

Mit etwas mehr Arbeit können wir sogar .tostring()-Methoden für Typen unterstützen, die automatisch erkannt und verwendet werden.

Was der "beste" Weg ist, ist eine offene Frage.

Es gibt einige Möglichkeiten.

Als Erstes muss man sagen, dass das Überladen von std::to_string für einen benutzerdefinierten Typ nicht erlaubt ist. Wir können nur Vorlagenfunktionen und -klassen spezialisieren im std-Namespace für benutzerdefinierte Typen, und std::to_string ist keine Vorlagenfunktion.

Eine gute Methode, to_string zu behandeln, ähnelt einem Operator oder einer Implementierung von swap. d. h. zulassen, dass die Suche von Argumenten abhängig gemacht wird.

wenn wir also etwas in einen String konvertieren wollen, könnten wir schreiben:

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

unter der Annahme, dass x ein Objekt vom Typ X im Namespace Y war und ich ein int war, könnten wir dann Folgendes definieren:

namespace Y {

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

}

was jetzt bedeuten würde:

wenn Sie to_string(x) aufrufen, werden tatsächlich Y::to_string(const Y::X&) und ausgewählt

aufruf von to_string(i) wählt std::to_string(int) 

Wenn Sie weiter gehen, kann es sein, dass Sie möchten, dass der_Zeichen-Operator fast dasselbe wie der Operator << tut, sodass der eine als der andere geschrieben werden kann:

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

Hier ist eine alternative Lösung. Nichts unbedingt eleganter oder zu ausgefallener, aber es ist ein alternativer Ansatz. Es wird davon ausgegangen, dass alle Klassen, für die Sie to_string aufrufen möchten, eine ToString () - Funktion haben.

Hier ist eine Funktionsvorlage, die nur mit Objekten des Klassentyps funktioniert und eine ToString () - Funktion aufruft.

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

Vielleicht möchten wir auch, dass es mit std :: string funktioniert.

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

Hier ist ein Beispiel für den verwendeten Code. Beachten Sie den Dummy-Namespace to_s. Ich schätze, wenn Sie std :: to_string in die main-Funktion setzen, wird der Name unserer Template-Funktion verwendet. Wir müssen den Namen also indirekt einführen. Wenn jemand den richtigen Weg kennt, würde ich mich über einen Kommentar freuen.

    #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;
    }

Hier ist was ich habe:

str: eine Nice-Saite, d: 1.100000

a: 1.200000

a: eine schöne Saite

a: Ungültiger Staat

1
Nathan Chappell

Sie können Ihren eigenen to_string in einem eigenen Namespace definieren (z. B. foo).

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

Und benutze es als:

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

Leider können Sie keine eigene Version von to_string im Namespace std definieren, da Sie zum Standard 17.6.4.2.1 Namespace std [namespace.std] (Emphasis Mine)

Das Verhalten eines C++ - Programms ist undefiniert, wenn Deklarationen oder .__ hinzugefügt werden. Definitionen für Namespace std oder für einen Namespace innerhalb des Namespaces std Falls nicht anders angegeben. Ein Programm kann eine Vorlage hinzufügen Spezialisierung für jede Standardbibliotheksvorlage auf den Namensraum nur std wenn die Deklaration von einem benutzerdefinierten Typ und dem .__ abhängt. Die Spezialisierung erfüllt die Anforderungen der Standardbibliothek für die Originalvorlage und ist nicht ausdrücklich verboten.

1
101010

Dies hat bereits eine großartige Antwort, aber ich würde gerne eine Alternative vorschlagen. Feedback ist willkommen.

Wenn Sie den Funktionsnamen to_string nicht verlassen, können Sie Ihre eigene ToString-freie Funktionsvorlage mit Spezialisierungen für die von to_string unterstützten Typen implementieren:

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); }

Die Standardimplementierung hier gibt eine Zeichenfolge aus Hexadezimalwerten mit den Werten im Speicherbereich für die übergebene Klassenreferenz zurück, während die Spezialisierungen std :: to_string aufrufen. Dadurch wird jede Klasse "Zeichenfolge".

Dann müssen Sie nur eine eigene Spezialisierung für Ihre Klasse implementieren:

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

Sie möchten wahrscheinlich nur operator<<() etwas überladen:

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

Alternative:

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

Dann können Sie einfach folgendes tun:

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

    return 0;
}
0
Paul Evans

Sie können keine neuen Überladungen von to_string in den std-Namespace einfügen. Dies kann jedoch in Ihrem Namespace geschehen:

namespace my {
   using std::to_string;

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

Dann können Sie my::to_string für alle Typen verwenden.

int main()
{
    my_class my_object;

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