45

I know it seems too much Java or C#. However, is it possible/good/wise to make my own class valid as an input for the function std::to_string ? Example:

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

If there is no such thing (and I think that), what is the best way to do it?

Enlico
  • 23,259
  • 6
  • 48
  • 102
Humam Helfawi
  • 19,566
  • 15
  • 85
  • 160
  • 3
    Related [Is there a standard way to convert a class to a string](http://stackoverflow.com/q/33357480/1708801) – Shafik Yaghmour Oct 28 '15 at 19:11
  • Also http://stackoverflow.com/questions/234724/is-it-possible-to-serialize-and-deserialize-a-class-in-c – Lol4t0 Oct 28 '15 at 19:13
  • @ShafikYaghmour Yes it may be.. However, the solutions there is attempting to overload to_string rather than making the class to_stringable – Humam Helfawi Oct 28 '15 at 19:15
  • @Lol4t0 no my purpose is not about serialization by converting it to string – Humam Helfawi Oct 28 '15 at 19:15
  • I don't believe there's an overload of `std::to_string` that can be used for what you're wanting. In C++, unlike C#/Java, there is no base class that all classes inherit from, so there's no overridable/overloadable `ToString()` method. – GreatAndPowerfulOz Oct 28 '15 at 19:19
  • Also see [Is specialization of std::to_string for custom types allowed by the C++ standard?](http://stackoverflow.com/q/36533199/608639). The short answer is, `std::to_string` is not a template, so you can't specialize it. The other option is to add a name to the `std` namespace, and that's not allowed either. – jww Feb 21 '17 at 21:31

8 Answers8

30

What's the 'best' way is an open question.

There are a few ways.

The first thing to say is that overloading std::to_string for a custom type is not allowed. We may only specialise template functions and classes in the std namespace for custom types, and std::to_string is not a template function.

That said, a good way to treat to_string is much like an operator or an implementation of swap. i.e. allow argument-dependent-lookup to do the work.

so when we want to convert something to a string we could write:

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

assuming that x was an object of type X in namespace Y and i was an int, we could then define:

namespace Y {

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

}

which would now mean that:

invoking to_string(x) actually selects Y::to_string(const Y::X&), and

invoking to_string(i) selects std::to_string(int)

Going further, it may be that you want to_string to do much the same as operator<<, so then one can be written in terms of the other:

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();
  }
}
leemes
  • 44,967
  • 21
  • 135
  • 183
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • 9
    writing one in terms of the other *may* have performance issues if executed often within a tight loop. It's left to the reader to decide whether he wants to optimise at the expense of having to maintain two code paths or not. – Richard Hodges Oct 28 '15 at 19:34
  • This statement in the answer above is wrong: "overloading `std::to_string` for a custom type is _not allowed_" - [this answer provides an example of how to do exactly that](https://stackoverflow.com/a/69667101/6461882) – S.V Oct 22 '21 at 15:36
  • @S.V I'm afraid that answer is incorrect. Although it works in practice most of the time, doing so makes the program incorrect. Because no overloads of functions may be added to the `std::` namespace at all by user code. It is reserved for the standard library, – Richard Hodges Oct 22 '21 at 15:39
  • The fact that `std::` is reserved for the standard library does not stop anybody from adding functions to it in one's personal code. Such additions will not become parts of the standard library, of course, but they can be done. – S.V Oct 22 '21 at 15:47
  • Of course, [one has to be careful to not cause any undesirable side effects](https://en.cppreference.com/w/cpp/language/extending_std) – S.V Oct 22 '21 at 15:55
  • @S.V If you add such overloads in your own personal code, your code technically becomes "not c++". Furthermore, if you ever copy/paste your code somewhere else, it will pollute the code base you are pasting to. – Richard Hodges Oct 22 '21 at 16:02
  • The original question above asks: "is it possible/good/wise to make my own class valid as an input for the function `std::to_string`". So, is it _possible_? Yes, of course. Is it _good/wise_? Well, it is debatable. In one's personal code and if done carefully, it is probably fine. If the code will be shared or used as a part of a bigger project, it might backfire. – S.V Oct 22 '21 at 16:10
27

First, some ADL helping:

namespace notstd {
  namespace adl_helper {
    template<class T>
    std::string as_string( T&& t ) {
      using std::to_string;
      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) will do an ADL-lookup of to_string(blah) with std::to_string in scope.

We then modify your class:

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

and now notstd::to_string(my_object) finds the proper to_string, as does notstd::to_string(7).

With a touch more work, we can even support .tostring() methods on types to be auto-detected and used.

Live example

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • 2
    How is this better than Richard's solution? – Slava Oct 28 '15 at 19:25
  • 9
    @Slava It does not require manually `using std::to_string` before each use of `to_string`; you don't have to pollute the namespace. Instead, the pollution is done within the `notstd::adl_helper` namespace only. You simply call `notstd::to_string` every time. I use a `friend to_string`, but that is equivalent to a free function `to_string`. – Yakk - Adam Nevraumont Oct 28 '15 at 19:29
  • 6
    Richard approves of this product or service. – Richard Hodges Oct 18 '17 at 07:46
  • What benefit of `T&&` here, why not `const T&`? I cannot imaginate destructive `to_string`. – Peter Taran Apr 09 '20 at 11:20
  • @peter for example, to string from std string should be destructive. And similar situations where you can save work by ripping an already created string out of the object. – Yakk - Adam Nevraumont Apr 09 '20 at 11:28
  • It's counter-intuitive, as for me. And you're forced to use `to_string(std::move(x))` instead of simple `to_string(x)` every time, `x` is lvalue (non-temporary, to put it simply). But thanks, I get your answer. – Peter Taran Apr 10 '20 at 06:14
  • @peter You are never forced to use `std::move`; learn what forwarding references are. They look like rvalue references, but are not. – Yakk - Adam Nevraumont Apr 13 '20 at 08:55
  • Oh! You're right, I missed that `T&&` behaves differently depending on whether `T` is template parameter or isn't. – Peter Taran Apr 14 '20 at 06:20
  • 1
    @PeterTaran No, it forwarding references "behaves differently" is completely in the template parameter deduction rules. `using T=int&;` or `template void foo()` then `foo`: within the body of `foo`, `T&&` behaves the same if `T` is a template parameter or not. Regardless, go read up on forwarding references. Reference collapsing rules convert `T&&` where `T=int&` to `int&` regardless of if `T` is a template parameter or not. – Yakk - Adam Nevraumont Apr 14 '20 at 13:19
  • This gives me a `error: no matching function for call to 'to_string'`… `note: in instantiation of function template specialization 'notstd::adl_helper::as_string >' requested here return adl_helper::as_string(std::forward(t));`` – A T Apr 25 '21 at 06:32
  • @at https://ideone.com/9ubLLs maybe you are missing an include, your C++ standard version is wrong, or other typo. – Yakk - Adam Nevraumont Apr 25 '21 at 11:14
5

You could define your own to_string in its own namespace (e.g., foo).

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

And use it as:

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

Unfortunatelly, you can't define your own version of to_string in namespace std because acorrding to the standard 17.6.4.2.1 Namespace std [namespace.std] (Emphasis Mine):

The behavior of a C++ program is undefined if it adds declarations or definitions to namespace std or to a namespace within namespace std unless otherwise specified. A program may add a template specialization for any standard library template to namespace std only if the declaration depends on a user-defined type and the specialization meets the standard library requirements for the original template and is not explicitly prohibited.

101010
  • 41,839
  • 11
  • 94
  • 168
  • Thanks but is not it too confusing to have more than to_string in two different namespaces? No way to use the same one? – Humam Helfawi Oct 28 '15 at 19:19
  • @HumamHelfawi No, unfortunately you can't define `to_string` in `std` for your class. – 101010 Oct 28 '15 at 19:21
  • 1
    I think the last part of the quote(which is not in bold) is important. If `std::to_string()` were implemented as a template, you could make a specialization of `std::to_string()` for your user-defined type. However, it is implemented as an overloaded function, so you cannot. – Tolli Oct 13 '16 at 14:58
  • Great to know that the `std` library is marked as "non extensible by external software" (generally speaking). – Alexis Wilke Dec 03 '19 at 05:25
1

You probably just want to overload operator<<() something like:

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

Alternatively:

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

Then you can simply do:

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

    return 0;
}
Paul Evans
  • 27,315
  • 3
  • 37
  • 54
  • yes this true. but for example I can not do this : 'print_a_string(my_object)' – Humam Helfawi Oct 28 '15 at 19:17
  • Edited answer accordingly, simply put `print_a_string`'s behavior into the `operator <<()` overload (and get rid of `print_a_string` as it's probably no longer needed). Alternatively, use `'print_a_string()` instead of `i`. – Paul Evans Oct 28 '15 at 19:22
1

You can't add new overloads of to_string into std namespace, but you can do it in your namespace:

namespace my {
   using std::to_string;

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

Then you can use my::to_string for all types.

int main()
{
    my_class my_object;

    std::cout << my::to_string(my_object);
    std::cout << my::to_string(5);
}
Stas
  • 11,571
  • 9
  • 40
  • 58
1

Here's an alternate solution. Nothing necessarily more elegant or too fancy, but it is an alternate approach. It assumes that all classes you intend to call to_string on have a ToString() function.

Here is a function template which will only work with objects of class type, and call a ToString() function.

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

Maybe we want it to work with std::string as well.

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

Here is an example of the code in use. Note the dummy namespace to_s. I guess if you put using std::to_string in the main function, it steps on our template function name, so we have to introduce the name indirectly like this. If anyone knows the correct way to do this I would appreciate a comment.

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

Here's what I got:

str: a nice string, d: 1.100000

a: 1.200000

a: a nice string

a: Invalid State

Nathan Chappell
  • 2,099
  • 18
  • 21
0

This already has a great answer but I'd like to propose an alternative, feedback is welcome.

If you're not dead set on the to_string function name, you could implement your own ToString free function template, with specializations for the types supported by 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); }

The default implementation here returns a string of hex values with the values at the memory space for the class reference passed, while the specializations call std::to_string, this will make any class "stringable".

Then you just need to implement your own specialization for your class:

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

Hm, why such complicated answers? One can just add an overloaded to_string function to the std namespace:

// tested with gcc-11.2.0
#include <iostream>
#include <string>

// some custom class
struct C { int b; float c; };

namespace std {
    std::string to_string(const C & c) 
    { return "I am: "+std::to_string(c.b)+' '+std::to_string(c.c); }
}

int main(void) {
    C c; c.b = 3; c.c = 4.4;
    std::cout<<std::to_string(c)<<std::endl;
}

Output:

I am: 3 4.400000
S.V
  • 2,149
  • 2
  • 18
  • 41
  • This is illegal. By definition in the standard. – Richard Hodges Oct 22 '21 at 15:40
  • https://en.cppreference.com/w/cpp/language/extending_std says it is perfectly legal for _"Program-defined types are non-closure class types or enumeration types that are not part of the C++ standard library and not defined by the implementation, or closure type of non-implementation-provided lambda expressions (since C++11), or instantiation of program-defined specializations."_ Your down-vote is based purely on opinion that one should not do it and has nothing to do with whether it is legal. – S.V Oct 22 '21 at 17:02
  • read it again - it says "types", not "functions" – Richard Hodges Oct 22 '21 at 17:11
  • Exactly, I am overloading `std::to_string` for a _"program-defined type"_, which is _"class types or enumeration types that are not part of the C++ standard library and not defined by the implementation"_ so my case belongs to the list of _"a few exceptions"_ when _"to add declarations or definitions to namespace std"_ is OK to do. So, it is _"illegal"_ only within the boundaries of your opinion. – S.V Oct 22 '21 at 20:32
  • "It is undefined behavior to declare a full specialization of any standard library function template." – Richard Hodges Oct 22 '21 at 20:37
  • In fact the text you quote is from the section "Function templates and member functions of templates". You have not added a specialisation of a function template. You have injected a concrete function into the std namespace. This is absolutely illegal. I know you have convinced yourself otherwise, and I know it feels convenient to do it. But is is illegal. – Richard Hodges Oct 22 '21 at 20:43
  • `std::to_string` [is not a template](https://en.cppreference.com/w/cpp/string/basic_string/to_string). And beyond the point: _"It is allowed to add template specializations for any standard library function template to the namespace std only if the declaration depends on at least one program-defined type and the specialization satisfies all requirements for the original template"_. So, even if `std::to_string` was a template, I would be doing it for a _"program-defined type"_ and so it would still be OK to do. – S.V Oct 22 '21 at 20:43
  • Actually, I am quoting ["Program-defined types"](https://en.cppreference.com/w/cpp/language/extending_std#Program-defined_types) section. – S.V Oct 22 '21 at 20:48
  • The section you are quoting is designed to cover things like specialising std::hash for some UDT T. These cases where it's legal are specifically noted in the standard (cppreference is not the full text of the standard). Note also that std::to_string is not a template. It is an overload set of concrete functions. https://en.cppreference.com/w/cpp/string/basic_string/to_string adding your own overload is not allowed. – Richard Hodges Oct 22 '21 at 20:50
  • if you doubt me, ask a new question about whether what you propose is legal. Tag is c++ and "language lawyer". Then sit back and wait for the storm. – Richard Hodges Oct 22 '21 at 20:51
  • https://stackoverflow.com/questions/69683163 – S.V Oct 22 '21 at 21:18
  • Well, all I got is that it is a potentially dangerous idea, but nobody was able to tell if it illegal from the C++ standard point view. – S.V Oct 22 '21 at 22:11