Создание пользовательского класса std:: to_string (возможно)

Я знаю, что слишком много Java или С#. Однако возможно ли/добро/разумно сделать мой собственный класс действительным как вход для функции std::to_string? Пример:

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

Если нет такой вещи (и я думаю, что), что это лучший способ сделать это?

Ответы

Ответ 1

Во-первых, некоторые ADL помогают:

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) выполнит ADL-поиск to_string(blah) с std::to_string в области видимости.

Затем мы модифицируем ваш класс:

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

и теперь notstd::to_string(my_object) находит правильное to_string, как и notstd::to_string(7).

С большим количеством работы мы можем даже поддерживать методы .tostring() для типов, которые будут автоматически обнаружены и использованы.

Ответ 2

Какой "лучший" способ - открытый вопрос.

Есть несколько способов.

В первую очередь следует сказать, что перегрузка std::to_string для настраиваемого типа недопустима. Мы можем специализировать функции и классы шаблона в пространстве имен std для пользовательских типов, а std::to_string не является функцией шаблона.

Тем не менее, хороший способ лечения to_string очень похож на оператор или реализацию swap. то есть разрешить зависящий от аргументов поиск для выполнения работы.

поэтому, когда мы хотим преобразовать что-то в строку, мы могли бы написать:

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

предполагая, что x является объектом типа X в пространстве имен Y, а я - int, мы могли бы определить:

namespace Y {

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

}

который теперь означает, что:

вызов to_string(x) на самом деле выбирает Y::to_string(const Y::X&) и

вызов to_string(i) выбирает std::to_string(int)

Далее, может быть, вы хотите, чтобы to_string выполнял то же самое, что и оператор < < < < <, поэтому можно написать в терминах другого:

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

Ответ 3

Вы можете определить свой собственный to_string в своем собственном пространстве имен (например, foo).

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

И используйте его как:

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

К сожалению, вы не можете определить свою собственную версию to_string в пространстве имен std, потому что acorrding к стандарту 17.6.4.2.1 Пространство имен std [namespace.std] (Emphasis Mine)

Поведение программы на С++ undefined, если оно добавляет объявления или определения в пространство имен std или в пространство имен в пространстве имен std если не указано иное. Программа может добавить шаблон специализация для любого стандартного шаблона библиотеки для пространства имен только std если декларация зависит от пользовательского типа и специализация соответствует требованиям стандартной библиотеки для оригинальный шаблон и явно не запрещен.

Ответ 4

Здесь альтернативное решение. Ничто не обязательно более изящно или слишком причудливо, но это альтернативный подход. Предполагается, что все классы, для которых вы собираетесь вызывать to_string, имеют функцию ToString().

Вот шаблон функции, который будет работать только с объектами типа класса и вызывать функцию ToString().

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

Может быть, мы хотим, чтобы он работал и с std :: string.

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

Вот пример используемого кода. Обратите внимание на фиктивное пространство имен to_s. Я предполагаю, что если вы используете std :: to_string в главной функции, это влияет на имя функции нашего шаблона, поэтому мы должны вводить имя косвенно, как это. Если кто-нибудь знает правильный способ сделать это, я был бы признателен за комментарий.

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

Вот что я получил:

str: хорошая строка, d: 1.100000

а: 1.200000

A: хорошая строка

а: недействительный штат

Ответ 5

Вероятно, вы просто хотите перегрузить operator<<() что-то вроде:

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

В качестве альтернативы:

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

Тогда вы можете просто сделать:

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

    return 0;
}

Ответ 6

Вы не можете добавить новые перегрузки to_string в пространство имен std, но вы можете сделать это в своем пространстве имен:

namespace my {
   using std::to_string;

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

Затем вы можете использовать my::to_string для всех типов.

int main()
{
    my_class my_object;

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

Ответ 7

На это уже есть отличный ответ, но я хотел бы предложить альтернативу, обратная связь приветствуется.

Если вы не полностью настроены на имя функции to_string, вы можете реализовать свой собственный бесплатный шаблон функции ToString со специализациями для типов, поддерживаемых 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); }

Реализация по умолчанию здесь возвращает строку шестнадцатеричных значений со значениями в области памяти для переданной ссылки на класс, в то время как специализации вызывают std :: to_string, это сделает любой класс "строковым".

Тогда вам просто нужно реализовать свою специализацию для своего класса:

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