覆盖std::to_string来为用户定义的枚举提供to_string是合适的方式吗?

21

C ++ 没有获取枚举的字符串表示形式的方法。人们通过编写包含大量样板代码的自定义函数(即 switch with case XYZ return "XYZ";)来解决这个问题。

当然,这需要枚举的用户知道自定义函数的名称。

所以,我想我可以向 std::to_string 添加一个特化以使用户能够在我的枚举上使用 to_string。类似于以下内容:

//
#include <iostream>
#include <string>
#include <cassert>
#define TEST
class Car
{
public:
    enum class Color
    {
        Red,
        Blue,
        White
    };
};
#ifdef TEST
#include <string>
namespace std
{
    std::string to_string (Car::Color c)
    {
        switch (c)
        {
        case Car::Color::Red:
            return "Red";
        case Car::Color::Blue:
            return "Blue";
        case Car::Color::White:
            return "White";
        default:
            {
                assert(0);
                return "";
            }
        }
    }

}
#endif
int main()
{
    std::cout << std::to_string(Car::Color::White) << std::endl;

}

这个解决方案有没有问题?


4
我认为不允许重载标准函数,只能特化模板。 - chris
两个建议:首先,我建议你查看 "#define str(x) #x"(以及相关的 "#define glue(a,b) a ## b")是否适合您的需求并减少样板文件。但一定要将其包装在一个函数中以确保类型安全,并在完成后立即使用 "#undef"进行定义。其次,你在 switch 语句中忘记了 "break;" -- 这可能是不太能够通过单元测试的原因,所以如果你没有感到超级单元测试,请谨慎使用。 - FizzixNerd
1个回答

25
这并非“覆盖”(适用于虚拟函数),你也没有添加“特化”(适用于模板),而是添加了一个重载,它在命名空间std中添加了新函数的声明和定义,这是被禁止的:

17.6.4.2.1 命名空间std [namespace.std]
如果C++程序向命名空间stdstd命名空间内的命名空间添加声明或定义,除非另有规定,否则将导致程序的行为未定义。程序只能在声明依赖于用户定义类型且特化满足原始模板的标准库要求且未被显式禁止时,才能将任何标准库模板的模板特化添加到命名空间std中。

更好的解决方案是在自己的命名空间中进行重载,然后调用to_string(c)而不是std::to_string(c)。这将找到正确的函数,而您无需添加任何内容到std

等一下,我有点困惑,你是指“将新函数的声明和定义添加到std命名空间”吗?to_string存在于std中,对吧?你认为参数是函数的一部分,所以才称它为新函数吗? - NoSenseEtAl
1
是的,这是一个新函数,不是吗?以前没有这个。它不是一个新的“名称”,而是你正在定义一个新函数。函数std::to_string(int)std::to_string(double)std::to_string(Car::Color)是不同的函数。 - Jonathan Wakely
3
C++标准认为函数的参数类型也是函数的一部分,这不仅仅是我的看法:1.3.17 [defns.signature] signature <function> 包括函数名、参数类型列表(8.3.5)和封闭的命名空间(如果有的话)。 - Jonathan Wakely
5
这是“using std::to_string”的一个好用法之一。将to_string放入enum的名称空间中,如果在不加上::限定符使用to_string,那么实参查找会发现它。只有当您处理的类型是通用类型时,在局部范围内使用using std::to_string - Yakk - Adam Nevraumont
1
@GaspardPetit,不是这样的,你根本不会失去这个可能性:template<class T> std::string foo(const T& obj) { using std::to_string; return to_string(obj); } 这将在 T 的相关命名空间中查找 std::to_string 或另一个 to_string。 (在发布您自己的评论之前,您是否阅读了Yakk的评论?)向命名空间 std 添加定义是未定义行为,并可能破坏您的std::lib。拒绝就可以了。 - Jonathan Wakely
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接