سؤال

When using enums, I usually have a few auxiliary methods associated with them. For C-style enums, I usually did this:

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

C++11 enum classes force you to use annoying prefixes:

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

What are my options for getting type safety, with the namespace-like scoping?

هل كانت مفيدة؟

المحلول

This is the way to go:

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

which is pretty much the same thing you would do with any other user-defined type. Try the above code here. Notice that in the example you can "convert" to string any equal comparable type.

نصائح أخرى

If you use the C++11 enum class the way you suggested, including the namespace, indeed you will need two qualifiers to access them: Colour::Colour::RED, which you may find annoying.

However, I don't think it is useful – neither for type safety nor for any other reason – to put the from_string and to_string functions into a namespace.

to_string() applies to many types, not only Colour. In fact, since C++11, there is even std::to_string, which you can apply to various built-in types to transform them into std::string. It makes sense to simply extend that notion to cover user-defined types:

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

You can then use to_string(42) as well as to_string(Color::red), and this is completely type-safe (as type-safe as any function overload / template specialization).

Similarly for 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");
}

I have provided only the implementation for Color, but it would be straight-forward to add it for other types.

Using this is type-safe and requires the explicit specification of Color (as template argument or scope qualifier) only where necessary, without duplication:

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

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

  return 0;
}

(If you are afraid of name-clashes, or generally don't want to put anything in global scope, you can put to_string and from_string into a namespace convert and use them as convert::from_string<Color>("red") etc. Or even, create a class template convert such that you can call it as convert<Color>::from_string("red"), which result in very easy-to-ready, intuitive code.)

What are my options for getting type safety, with the namespace-like scoping?

This is type safe:

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

You can decide to put everything inside a namespace. The enum's type behaves like any other user defined type. However, whereas the first method needs no type information (it could be called enum_to_string), the second one needs either a name involving the type of the enum, a namespace, or to be a function template. This is because you cannot overload based on return type. So, you can put everything in a namespace, and take advantage of argument dependent look-up when using the enum-to-string method:

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
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top