При использовании перечислений у меня обычно есть несколько вспомогательных методов, связанных с ними. Для перечислений в стиле 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&);
Какие у меня есть варианты для обеспечения безопасности типов с областью, подобной пространству имен?
Это путь:
#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;
}
это почти то же самое, что вы сделали бы с любым другим определяемым пользователем типом. Попробуйте приведенный выше код Вот. Обратите внимание, что в этом примере вы можете «преобразовать» в строку любой равный сопоставимый тип.
Если вы используете C ++ 11 enum class
Как вы и предлагали, включая пространство имен, вам понадобятся два квалификатора для доступа к ним: Colour::Colour::RED
, что вы можете найти раздражающим.
Однако я не думаю, что это полезно — ни для безопасности типов, ни для каких-либо других причин — ставить from_string
а также to_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_string
а также from_string
в пространство имен convert
и использовать их как convert::from_string<Color>("red")
и т.д. Или даже создать шаблон класса convert
так что вы можете назвать это как convert<Color>::from_string("red")
, что приводит к очень легко готовому, интуитивно понятному коду.)
Какие у меня есть варианты для обеспечения безопасности типов с областью, подобной пространству имен?
Это безопасный тип:
enum class Colour { RED, BLUE, GREEN };
string colour_to_string(Colour);
Colour from_string(string const&);
Вы можете решить поместить все в пространство имен. Тип enum ведет себя как любой другой определенный пользователем тип. Однако, в то время как первый метод не нуждается в информации о типе (он может быть вызван 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
}