使用户定义的类 std::to_string-able

Making a user-defined class std::to_string-able

本文关键字:to string-able std 用户 定义      更新时间:2023-10-16

我知道这似乎太多了Java或C#。但是,是否有可能/好/明智地使我自己的类作为函数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);
}

如果没有这样的事情(我认为(,最好的方法是什么?

什么是

"最佳"方式是一个悬而未决的问题。

有几种方法。

首先要说的是,不允许重载自定义类型的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执行与运算符<<大致相同的操作,因此可以编写一个

运算符:
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();
  }
}

首先,一些 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 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)找到了合适的to_stringnotstd::to_string(7)也是如此。

通过更多的工作,我们甚至可以支持.tostring()方法自动检测和使用的类型。

现场示例

您可以在

自己的命名空间中定义自己的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 命名空间 std [namespace.std](强调我的(:

如果C++程序添加声明或 对命名空间 std 或命名空间 std 中的命名空间的定义 除非另有说明。程序可以添加模板 仅将任何标准库模板专用化为命名空间 std 如果声明依赖于用户定义类型,并且 专业化满足标准库要求 原始模板,未明确禁止。

您可能只想重载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重载添加到命名空间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);
}

这是一个替代解决方案。 没有什么比这更优雅或太花哨的了,但这是一种替代方法。 它假定您打算调用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:一根漂亮的字符串

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

此处的默认实现返回一串十六进制值,其中包含传递的类引用的内存空间处的值,而专用化调用 std::to_string,这将使任何类"可串"。

然后,您只需要为您的类实现自己的专业化:

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

嗯,为什么会有这么复杂的答案?可以将重载的 to_string 函数添加到 std 命名空间:

// 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