【问题标题】:Type safe enums and scoping auxiliary functions类型安全枚举和范围辅助功能
【发布时间】:2013-07-18 06:27:05
【问题描述】:

当使用枚举时,我通常有一些与之相关的辅助方法。对于 C 风格的枚举,我通常这样做:

namespace Colour {
    enum Enum { RED, BLUE, GREEN };
    string to_string(Enum);
    Enum from_string(string const&);
}

C++11 枚举类强制你使用烦人的前缀:

enum class Colour { RED, BLUE, GREEN };
string colour_to_string(Enum);
Enum colour_to_string(string const&);

使用类似命名空间的作用域,我有哪些选择来获得类型安全?

【问题讨论】:

  • 枚举类有什么问题?
  • 您要解决哪个问题?那些“烦人的前缀”是作用域,也是 C++11 枚举动机的一部分。
  • @Rapptz:我不能做 Colour::to_string/Colour::from_string。
  • @juanchopanza:我将阅读有关枚举类的论文,因为我发现它们确实有限制。例如你不能定义 operator|与类或普通枚举一样可靠。

标签: c++ c++11 syntactic-sugar


【解决方案1】:

这是要走的路:

#include <string>
#include <iostream>

enum class Colors{ RED, BLUE, GREEN };

struct myRedStruct{
    bool operator==(const Colors& c)const{
        if(c == Colors::RED)
            return true;
        return false;
    }
};

namespace ColorsUtils {
    using namespace std;

    template <typename ColorComparable>
    string to_string(const ColorComparable& c){
        if(c == Colors::RED)
            return "red";
        return "not red";
    }
    Colors from_string(string const&);
}

int main() {
    Colors c = Colors::BLUE;
    const auto& s = ColorsUtils::to_string(c);
    std::cout << s << std::endl;

    myRedStruct mrs;
    const auto & s2 = ColorsUtils::to_string(mrs);
    std::cout << s2 << std::endl;
}

这与您对任何其他用户定义类型所做的几乎相同。试试上面的代码here。请注意,在示例中,您可以将任何相等的可比较类型“转换”为字符串。

【讨论】:

  • 我决定采用这种类型的解决方案,尽管对于我的应用程序来说,ColorComparable 有点过头了。谢谢
【解决方案2】:

如果您按照您建议的方式使用 C++11 enum class,包括命名空间,您确实需要两个限定符来访问它们:Colour::Colour::RED,您可能会觉得这很烦人。

但是,我认为将 from_stringto_string 函数放入命名空间中没有用处——无论是出于类型安全还是出于任何其他原因。

to_string() 适用于多种类型,而不仅仅是Colour。事实上,从C++11开始,甚至还有std::to_string,你可以将它应用到各种内置类型上,将它们转换成std::string。简单地扩展该概念以涵盖用户定义的类型是有意义的:

template <typename T>
std::string to_string(const T arg)
{ return std::to_string(arg); }

template <>
std::string to_string(const Color c)
{
  switch (c)
    {
    case Color::red:
      return "red";
    case Color::green:
      return "green";
    case Color::blue:
    default:
      return "blue";
    }
}

然后您可以使用to_string(42)to_string(Color::red),这是完全类型安全的(与任何函数重载/模板特化一样类型安全)。

同样适用于from_string

template <typename T>
T from_string(const std::string &str);

template <>
Color from_string<Color>(const std::string &str)
{
  if (str == "red")
    return Color::red;
  else if (str == "green")
    return Color::green;
  else if (str == "blue")
    return Color::blue;
  else
    throw std::invalid_argument("Invalid color");
}

我只提供了Color 的实现,但是为其他类型添加它会很简单。

使用 this 是类型安全的,并且仅在必要时需要明确指定 Color(作为模板参数或范围限定符),无需重复:

int main()
{
  Color c = Color::red;

  std::cout << to_string(c) << std::endl;
  c = from_string<Color>("red");

  return 0;
}

(如果你害怕名字冲突,或者一般不想把任何东西放在全局范围内,你可以把 to_stringfrom_string 放到命名空间 convert 中,并将它们用作 convert::from_string&lt;Color&gt;("red") 等. 甚至,创建一个类模板convert,这样您就可以将其命名为convert&lt;Color&gt;::from_string("red"),从而生成非常易于准备、直观的代码。)

【讨论】:

    【解决方案3】:

    使用类似命名空间的作用域,我有哪些选择来获得类型安全?

    这是类型安全的:

    enum class Colour { RED, BLUE, GREEN };
    string colour_to_string(Colour);
    Colour from_string(string const&);
    

    您可以决定将所有内容放在命名空间中。枚举类型的行为类似于任何其他用户定义的类型。然而,第一种方法不需要类型信息(它可以称为enum_to_string),而第二种方法需要涉及枚举类型的名称、命名空间或作为函数模板。这是因为您不能根据返回类型重载。因此,您可以将所有内容放在命名空间中,并在使用 enum-to-string 方法时利用参数相关查找:

    namespace colour
    {
      enum class Colour { RED, BLUE, GREEN };
      string to_string(Colour);
      Colour from_string(string const&);
    }
    
    int main()
    {
      using colour::Colour;
      Colour c{Colour::RED};
      string s = to_string(c); // ADL kicks in
      Colour c2 = colour::from_string("RED"); // no ADL, must specify namespace
    }
    

    【讨论】:

    • 我看到的越多,我就越不明白这一点,所以我会问:你到底为什么要提取任何给定 ENUM 的“真实”值?那时 ENUM 的意义何在?
    • @user2485710 你的意思是基础价值?我认为这里没有人试图这样做。问题是关于字符串的转换,以及如何组织命名空间。
    • @jogojapan 你怎么解释这个string to_string(Colour); 呢?
    • @user2485710 据我了解,这个想法是将值Colour::red 转换为字符串"red",例如用于漂亮的打印目的。当打印枚举变量的值时,这在调试或日志消息中很有用。
    • @user2485710 嗯,它想得到一个枚举值的字符串表示?这是一个非常常见的用例。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-06-15
    • 2019-08-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多