问题
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?
回答1:
First, some ADL helping:
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)
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.
回答2:
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();
}
}
回答3:
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:
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);
}
回答5:
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
回答6:
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); }
回答7:
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;
}
来源:https://stackoverflow.com/questions/33399594/making-a-user-defined-class-stdto-stringable