制作用户定义的 class std::to_string-able
Making a user-defined class std::to_string-able
我知道这似乎太多了 Java 或 C#。但是,是否 possible/good/wise 使我自己的 class 有效作为函数 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);
}
如果没有这样的事情(我认为),最好的方法是什么?
您可能只想重载 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;
}
您可以在自己的命名空间中定义自己的 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);
}
不幸的是,您不能在命名空间 std
中定义自己的 to_string
版本,因为根据标准 17.6.4.2.1 命名空间标准 [namespace.std] (强调我的):
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.
什么是'best'方式是一个悬而未决的问题。
有几种方法。
首先要说的是,不允许 为自定义类型重载 std::to_string
。我们只能在自定义类型的 std
命名空间中 专门化模板函数和 类,并且 std::to_string
不是模板函数。
也就是说,处理 to_string
的好方法很像 swap
的运算符或实现。即允许参数依赖查找来完成工作。
所以当我们想将某些东西转换为字符串时,我们可以这样写:
using std::to_string;
auto s = to_string(x) + " : " + to_string(i);
假设 x 是命名空间 Y 中 X 类型的对象,而 i 是一个 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 做与 operator<< 相同的事情,因此可以将一个写成另一个:
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();
}
}
您不能将 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);
}
首先,一些 ADL 帮助:
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)
将在 std::to_string
范围内对 to_string(blah)
进行 ADL 查找。
然后我们修改你的class:
class my_class{
public:
friend std::string to_string(my_class const& self) {
return "I am " + notstd::to_string(self.i);
}
int i;
};
现在 notstd::to_string(my_object)
和 notstd::to_string(7)
.
一样找到了合适的 to_string
只要多做一点工作,我们甚至可以支持 .tostring()
自动检测和使用类型的方法。
这是另一种解决方案。没有什么比这更优雅或太花哨了,但它是另一种方法。它假定您打算调用 to_string 的所有 classes 都具有 ToString() 函数。
这是一个函数模板,它只适用于 class 类型的对象,并调用 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。我猜如果你把 using std::to_string 放在 main 函数中,它会踩到我们的模板函数名称,所以我们必须像这样间接引入名称。如果有人知道正确的方法,我将不胜感激。
#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: 一个不错的字符串
a:无效状态
这已经有了很好的答案,但我想提出一个替代方案,欢迎提供反馈。
如果您对 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); }
此处的默认实现 returns 一串十六进制值,其值在内存 space 中用于传递 class 引用,而特化调用 std::to_string,这将使任何 class "stringable".
那么你只需要为你的 class:
实施你自己的专业化
template<> std::string ToString(const my_class& t) { return "I am " + std::to_string(t.i); }
嗯,为什么这么复杂的答案?可以只向 std
命名空间添加一个重载的 to_string
函数:
// 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;
}
输出:
I am: 3 4.400000
我知道这似乎太多了 Java 或 C#。但是,是否 possible/good/wise 使我自己的 class 有效作为函数 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);
}
如果没有这样的事情(我认为),最好的方法是什么?
您可能只想重载 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;
}
您可以在自己的命名空间中定义自己的 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);
}
不幸的是,您不能在命名空间 std
中定义自己的 to_string
版本,因为根据标准 17.6.4.2.1 命名空间标准 [namespace.std] (强调我的):
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.
什么是'best'方式是一个悬而未决的问题。
有几种方法。
首先要说的是,不允许 为自定义类型重载 std::to_string
。我们只能在自定义类型的 std
命名空间中 专门化模板函数和 类,并且 std::to_string
不是模板函数。
也就是说,处理 to_string
的好方法很像 swap
的运算符或实现。即允许参数依赖查找来完成工作。
所以当我们想将某些东西转换为字符串时,我们可以这样写:
using std::to_string;
auto s = to_string(x) + " : " + to_string(i);
假设 x 是命名空间 Y 中 X 类型的对象,而 i 是一个 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 做与 operator<< 相同的事情,因此可以将一个写成另一个:
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();
}
}
您不能将 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);
}
首先,一些 ADL 帮助:
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)
将在 std::to_string
范围内对 to_string(blah)
进行 ADL 查找。
然后我们修改你的class:
class my_class{
public:
friend std::string to_string(my_class const& self) {
return "I am " + notstd::to_string(self.i);
}
int i;
};
现在 notstd::to_string(my_object)
和 notstd::to_string(7)
.
to_string
只要多做一点工作,我们甚至可以支持 .tostring()
自动检测和使用类型的方法。
这是另一种解决方案。没有什么比这更优雅或太花哨了,但它是另一种方法。它假定您打算调用 to_string 的所有 classes 都具有 ToString() 函数。
这是一个函数模板,它只适用于 class 类型的对象,并调用 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。我猜如果你把 using std::to_string 放在 main 函数中,它会踩到我们的模板函数名称,所以我们必须像这样间接引入名称。如果有人知道正确的方法,我将不胜感激。
#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: 一个不错的字符串
a:无效状态
这已经有了很好的答案,但我想提出一个替代方案,欢迎提供反馈。
如果您对 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); }
此处的默认实现 returns 一串十六进制值,其值在内存 space 中用于传递 class 引用,而特化调用 std::to_string,这将使任何 class "stringable".
那么你只需要为你的 class:
实施你自己的专业化template<> std::string ToString(const my_class& t) { return "I am " + std::to_string(t.i); }
嗯,为什么这么复杂的答案?可以只向 std
命名空间添加一个重载的 to_string
函数:
// 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;
}
输出:
I am: 3 4.400000