是否为C++标准允许的自定义类型的std :: to_string专门化?

jot*_*tik 36 c++ std tostring specialization c++11

在C++ 11及更高版本中,是否允许专用std::to_stringstd自定义类型的命名空间?

namespace std {
string to_string(::MyClass const & c) { return c.toString(); }
}
Run Code Online (Sandbox Code Playgroud)

示例用例:

int main() {
    MyClass c;
    std::cout << std::to_string(c) << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

Jam*_*son 33

在C++ 11及更高版本中,是否允许在std命名空间中为自定义类型专门化std :: to_string?

不.首先,它不是模板功能,所以你根本不能专门化它.

如果您要求添加自己的过载功能,答案仍然保持不变.

来自扩展命名空间std的文档片段:

将声明或定义添加到命名空间std或嵌套在std中的任何命名空间是未定义的行为,下面列出了一些例外情况

只有当声明取决于用户定义的类型且专业化满足原始模板的所有要求时,才允许将任何标准库模板的模板特化添加到命名空间std,除非禁止此类特殊化.


在实践中,一切都可能正常工作,但严格来说,标准说不能保证会发生什么.


编辑:我无法访问官方标准,因此以下内容来自免费工作草案(N4296):

17.6.4.2命名空间使用

17.6.4.2.1命名空间std

  1. 如果C++程序向命名空间std或命名空间std中的命名空间添加声明或定义,则它是未定义的,除非另有说明.只有当声明取决于用户定义的类型并且特化符合原始模板的标准库要求且未明确禁止时,程序才可以将任何标准库模板的模板特化添加到命名空间std.181
  2. 如果声明,C++程序的行为是不确定的

    2.1 - 标准库类模板的任何成员函数的显式特化,或

    2.2 - 标准库类或类模板的任何成员函数模板的显式特化,或

    2.3 - 标准库类或类模板的任何成员类模板的显式或部分特化.

    仅当声明取决于用户定义类型的名称并且实例化符合原始模板的标准库要求时,程序才可以显式实例化标准库中定义的模板.

  3. 转换单元不应将namespace std声明为内联命名空间(7.3.1).

  • @Brandon:我们通常希望编译器在我们做禁止的事情时出错,但要求编译器检测和诊断这类事情是不现实的.因此,UB. (10认同)
  • 不保证..未定义?我不敢相信这是未定义的行为.专门针对任何其他命名空间工作得很好.我非常怀疑在程序行为方面会有任何后果.我认为标准应该只是说它是禁止的,如果它真的没有,就不要把它称为未定义.这怎么会导致未定义的行为? (3认同)
  • 17.6.4.2 引用中引用的脚注:*181) 实例化其他库模板的任何库代码都必须准备好与任何用户提供的满足标准最低要求的专业化充分配合。* (2认同)

sjr*_*son 11

如果我没弄错,你可以简单地重载to_string泛型:

template<typename T> to_string(const T& _x) {
    return _x.toString();
}
Run Code Online (Sandbox Code Playgroud)

这允许程序使用ADL(参数依赖查找)来to_string根据传递的类型正确选择相关方法.

  • @JamesAdkison在它之外 (7认同)
  • 您是否建议在“ std”名称空间中或外部进行此操作? (3认同)
  • @JustinTime>如果它的写法不正确.正确的方法是`使用std :: to_string; to_string(myObject);`无论`myObject`的类型如何,这都会做正确的事情. (3认同)
  • 这仅适用于像 `to_string(myObject)` 这样的非限定函数调用,但不适用于像 `std::to_string(myObject)` 这样的限定函数调用。 (2认同)

W.F*_*.F. 8

更好的方法是创建自己的函数,std::to_string如果可能的话,可以使用.toString()方法,只要它可用于传递参数:

#include <type_traits>
#include <iostream>
#include <string>

struct MyClass {
   std::string toString() const { return "MyClass"; }
};

template<class T>
typename std::enable_if<std::is_same<decltype(std::declval<const T&>().toString()), std::string>::value, std::string>::type my_to_string(const T &t) {
    return t.toString();
}

template<class T>
typename std::enable_if<std::is_same<decltype(std::to_string(std::declval<T&>())), std::string>::value, std::string>::type my_to_string(const T &t) {
    return std::to_string(t);
}

int main() {
   std::cout << my_to_string(MyClass()) << std::endl; // will invoke .toString
   std::cout << my_to_string(1) << std::endl; //will invoke std::to_string
}
Run Code Online (Sandbox Code Playgroud)


Mak*_*s_F 7

在 C++11 及更高版本中,是否允许在 std 命名空间中为自定义类型专门化 std::to_string?

不,你不能添加过载到std命名空间to_string()

好消息是您不需要这样做,有一个简单的解决方案!

您可以提供自己的实现,让 ADL(参数相关查找)为您解决问题。

就是这样:

class A {};

std::string to_string(const A&)
{
    return "A()";
}

int main()
{
    A a;
    using std::to_string;
    std::cout << to_string(2) << ' ' << to_string(a);
}
Run Code Online (Sandbox Code Playgroud)

这里我们使用了using 声明来引入std::to_string作用域,然后我们使用了对 的非限定调用to_string()

现在,std::to_string::to_string都是可见的,编译器会选择适当的重载。

如果你不想using std::to_stringto_string每次使用前写或者你害怕在to_string没有命名空间的情况下忘记使用,你可以创建一个辅助函数

template<typename T>
std::string my_to_string(T&& t)
{
    using std::to_string;
    return to_string(std::forward<T>(t));
}
Run Code Online (Sandbox Code Playgroud)

请注意,此函数可以在任何命名空间中定义,并且与定义类的命名空间无关(它们不必相同)。

请参阅示例

注意:如果您是来电者,此方法有效to_string。如果有一个库调用std::to_string并且您想为您的类型更改它,那么您就不走运了。