键入安全枚举和作用域辅助功能

时间:2013-07-19 04:30:25

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

使用枚举时,我通常会有一些与之相关的辅助方法。对于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&);

使用类似命名空间的范围,我有什么选择获得类型安全性?

3 个答案:

答案 0 :(得分:3)

这是要走的路:

#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。请注意,在示例中,您可以“转换”为字符串任何相同的可比类型。

答案 1 :(得分:1)

  

使用类似命名空间的范围,我有什么选择获得类型安全性?

这是类型安全的:

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

您可以决定将所有内容放在命名空间中。枚举类型的行为与任何其他用户定义的类型相同。但是,第一种方法不需要类型信息(可以称为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
}

答案 2 :(得分:1)

如果您按照建议的方式使用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的实现,但将其添加到其他类型中会很简单。

使用它是类型安全的,只有在必要时才需要显式指定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<Color>("red")等。或者甚至创建一个类模板convert,以便您可以将其称为convert<Color>::from_string("red"),从而生成非常易于使用的直观代码。)