Lilia O.
Lilia O.

Reputation: 87

Get & Set for Enum in C++

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

Answers (5)

Tony Delroy
Tony Delroy

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

antron
antron

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

Aaditya Kalsi
Aaditya Kalsi

Reputation: 1049

One approach would be to replace:

 SetSchoolType(2);

with

 SetSchoolType(static_cast<SchoolType>(2));

Upvotes: 0

user1598725
user1598725

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

Anon Mail
Anon Mail

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:

  1. Validates the input
  2. Does the necessary conversion from string to enum.

Upvotes: 0

Related Questions