56

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?

6
  • 4
    Related Is there a standard way to convert a class to a string Commented Oct 28, 2015 at 19:11
  • Also stackoverflow.com/questions/234724/… Commented Oct 28, 2015 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 Commented Oct 28, 2015 at 19:15
  • @Lol4t0 no my purpose is not about serialization by converting it to string Commented Oct 28, 2015 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. Commented Oct 28, 2015 at 19:19

8 Answers 8

37

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();
  }
}
Sign up to request clarification or add additional context in comments.

7 Comments

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.
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
@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,
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.
|
28

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

11 Comments

How is this better than Richard's solution?
@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.
Richard approves of this product or service.
What benefit of T&& here, why not const T&? I cannot imaginate destructive to_string.
@PeterTaran No, it forwarding references "behaves differently" is completely in the template parameter deduction rules. using T=int&; or template<class T> void foo() then foo<int&>: 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.
|
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.

4 Comments

Thanks but is not it too confusing to have more than to_string in two different namespaces? No way to use the same one?
@HumamHelfawi No, unfortunately you can't define to_string in std for your class.
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.
Great to know that the std library is marked as "non extensible by external software" (generally speaking).
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;
}

2 Comments

yes this true. but for example I can not do this : 'print_a_string(my_object)'
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.
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);
}

2 Comments

The downside to this design is that you have to define all of your to_strings in namespace my, and not in the namespace of the type.
but you can have your type and the corresponding to_string() in the same namespace as long as it is not ::std.
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

Comments

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

Comments

-1

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

14 Comments

This is illegal. By definition in the standard.
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.
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.
"It is undefined behavior to declare a full specialization of any standard library function template."
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.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.