Reputation: 87
I have a class named School and one of its members is an enum 'SchoolType' that has two possible values: Primary and Secondary. Here is what I have for the enum in my School header file:
enum class SchoolType {
Primary = 1,
Secondary = 2
};
And in my main C++ file I have a function ReadSchool
that reads in all of School's members from user input. All of the other members are int
and string
values so I have no trouble reading in those but I don't know what to do for the enum. My best idea was to do this:
School ReadSchool()
{
School tempSchool;
int type;
cout << "Name of School: ";
tempSchool.SetName(GetString());
cout << endl << "Type of School (1 = Primary, 2 = Secondary): ";
cin >> type;
if (type = 2)
tempSchool.SetTypeSchool(2);
else
tempSchool.SetTypeSchool(1);
return tempSchool;
}
Putting an int value into my SetTypeSchool()
doesn't work, but nor does writing either Primary
or Secondary
. I'm using Visual Studio 2015 if that makes any difference. Can anyone offer some clarity or a better way to set my enum value? Thanks for any help
Upvotes: 2
Views: 2098
Reputation: 106096
You can write streaming operators for your enumeration type, after which they can be used naturally with <<
and >>
:
#include <iostream>
#include <string>
#include <sstream>
enum class SchoolType {
Primary = 1,
Secondary = 2
};
std::ostream& operator<<(std::ostream& os, SchoolType s)
{
return os << (s == SchoolType::Primary ? "Primary" :
s == SchoolType::Secondary ? "Secondary" :
throw std::runtime_error("invalid SchoolType output"));
}
std::istream& operator>>(std::istream& is, SchoolType& s)
{
std::string word;
if (is >> word)
{
if (word == "Primary") { s = SchoolType::Primary; return is; }
if (word == "Secondary") { s = SchoolType::Secondary; return is; }
is.setstate(std::ios::failbit);
}
return is;
}
int main()
{
std::cout << SchoolType::Primary << ' ' << SchoolType::Secondary << '\n';
std::istringstream iss("Primary Secondary Fail");
SchoolType a, b;
iss >> a >> b;
std::cout << a << ' ' << b << " good:" << iss.good() << '\n';
iss >> a;
std::cout << "good:" << iss.good() << '\n';
}
See the code run here.
CAUTION: The simple streaming to std::string
then checking for an enumeration identifier requires a little care in use: the identifier must be delimited by whitespace (e.g. space, tab, newline) or End-Of-File; trailing characters like punctuation or brackets/braces/parentheses will be sucked into the string, then the comparison will fail and throw.
A more complex but robust version is available here, using a supporting class Identifier
for character-by-character input and unget()
at the first character not legal in an identifier. With this, in operator>>(std::istream& is, SchoolType& s)
just replace std::string word;
with Identifier word;
.
struct Identifier : std::string { };
std::istream& operator>>(std::istream& is, Identifier& idn)
{
idn.clear();
char c;
if (is >> c)
{
if (!std::isalnum(c))
is.setstate(std::ios::failbit);
else
{
idn += c;
while (is.get(c))
if (std::isalnum(c) || c == '_')
idn += c;
else
{
is.unget();
break;
}
// hit EOF, is.get(c) will have left fail condition set
is.clear(); // doesn't matter that we also clear EOF
}
}
return is;
}
(In practice, I prefer to write to-string and from-string functions, then write the streaming operators using those: it's sometimes more convenient e.g. if you quickly want a string representation without creating streams).
Upvotes: 1
Reputation: 3847
Your code has several problems, for example if (type = 2)
should be if (type == 2)
. To pass the actual constants, you need to refer to them as SchoolType::Primary
and SchoolType::Secondary
. If your SetSchoolType
's argument is of type SchoolType
, those are the two constants it will accept.
Several answerers mentioned converting the value from string, including defining a stream operator for the purpose. If you are not against using an external library, you can achieve that with syntax like this:
#include <enum.h>
BETTER_ENUM(SchoolType, int, Primary = 1, Secondary = 2)
// ...later...
SchoolType type;
cin >> type; // Expects to read the string "Primary" or "Secondary".
The rest of the usage is, for the most part, like a normal enum class.
The library can be found here: https://github.com/aantron/better-enums. It consists of a single header file. The license is BSD, so use it as you wish. There is a Stack Overflow answer explaining the core of the library's internals: https://stackoverflow.com/a/31362042/2482998.
Due to an unfortunate limitation, you won't be able to declare SchoolType
directly inside School
as you describe, but there is an easy workaround shown in the README
.
Disclaimer: I am the author.
Upvotes: 1
Reputation: 1049
One approach would be to replace:
SetSchoolType(2);
with
SetSchoolType(static_cast<SchoolType>(2));
Upvotes: 0
Reputation: 60
SchoolType GetSchoolType(string input)
{
// DeHumanize our input
if(input == "Primary")
{
return SchoolType::Primary;
}
else if(input == "Secondary")
{
return SchoolType::Secondary;
}
return SchoolType.None; // or throw an exception, or return a default SchoolType; up to you
}
Upvotes: 2
Reputation: 4770
You should allow the user to enter strings (e.g. "Primary" or "Secondary") and then have a function that converts the strings to the enum
.
The function serves two purposes:
string
to enum
.Upvotes: 0