Tommy K
Tommy K

Reputation: 1807

How to make sure user enters allowed enum

I have to write a program with an Enum state, which is the 50 2-letter state abbreviations(NY, FL, etc). I need to make a program that asks for the user info and they user needs to type in the 2 letters corresponding to the state. How can I check that their input is valid i.e matches a 2 letter state defined in Enum State{AL,...,WY}? I suppose I could make one huge if statement checking if input == "AL" || ... || input == "WY" {do stuff} else{ error input does not match state }, but having to do that for all 50 states would get a bit ridiculous. Is there an easier way to do this?

Also if Enum State is defined as {AL, NY, FL}, how could I cast a user input, which would be a string, into a State? If I changed the States to {"AL", "NY", "FL"} would that be easier or is there another way to do it?

Upvotes: 0

Views: 100

Answers (4)

AngeloDM
AngeloDM

Reputation: 417

The solution is simple, but only for 2 characters in the string (as in your case):

#include <stdio.h>
#include <stdint.h>

enum TEnum
{
    AL = 'LA',
    NY = 'YN',
    FL = 'LF'
};


 int _tmain(int argc, _TCHAR* argv[])
{

    char* input = "NY";
    //char* input = "AL";
    //char* input = "FL";



    switch( *(uint16_t*)input )
    {
        case AL:
            printf("input AL");
            break;
        case NY:
            printf("input NY");
            break;
        case FL:
            printf("input FL");
            break;
    }
    return 0;
}

In above example I used an enumeration with a double character code (it is legal) and passed to the switch statement a input string. I tested it end work!. Notice the word alignment in enumeration.

Ciao

Upvotes: 0

James Kanze
James Kanze

Reputation: 153939

What you need is a table. Because the enums are linear, a simple table of strings would be sufficient:

char const* const stateNames[] =
{
    //  In the same order as in the enum.
    "NY",
    "FL",
    //  ...
};

Then:

char const* const* entry 
        = std::find( std::begin( stateNames ), std::end( stateNames ), userInput );
if (entry == std::end( stateNames ) ) {
    //  Illegal input...
} else {
    State value = static_cast<State>( entry - std::begin( stateNames ) );

Alternatively, you can have an array of:

struct StateMapping
{
    State enumValue;
    char const* name;
    struct OrderByName
    {
        bool operator()( StateMapping const& lhs, StateMapping const& rhs ) const
        {
            return std::strcmp( lhs.name, rhs. name ) < 0;
        }
        bool operator()( StateMapping const& lhs, std::string const& rhs ) const
        {
            return lhs.name < rhs;
        }
        bool operator()( std::string const& lhs, StateMapping const& rhs ) const
        {
            return lhs < rhs.name;
        }
    };
};
StateMapping const states[] = 
{
    { NY, "NY" },
    //  ...
};

sorted by the key, and use std::lower_bound:

StateMapping::OrderByName cmp;
StateMapping entry =
        std::lower_bound( std::begin( states ), std::end( states ), userInput, cmp );
if ( entry == std::end( states ) || cmp( userInput, *entry) {
    //  Illegal input...
} else {
    State value = entry->enumValue;
    //  ...
}

The latter is probably slightly faster, but for only fifty entries, I doubt you'll notice the difference.

And of course, you don't write this code manually; you generate it with a simple script. (In the past, I had code which would parse the C++ source for the enum definitions, and generate the mapping functionality from them. It's simpler than it sounds, since you can ignore large chunks of the C++ code, other than for keeping track of the various nestings.)

Upvotes: 0

Clifford
Clifford

Reputation: 93486

The simplest method is to use an STL std::map, but for academic exercises that may not be permitted (for example it may be required to use only techniques covered in the course material).

Unless explicitly initialised, enumerations are integer numbered sequentially starting from zero. Given that, you can scan a lookup-table of strings, and cast the matching index to an enum. For example:

enum eUSstate
{
    AL, AK, AZ, ..., NOSTATE
} ;

eUSstate string_to_enum( std::string inp )
{
    static const int STATES = 50 ;
    std::string lookup[STATES] = { "AL", "AK", "AZ" ... } ;

    int i = 0 ;
    for( i = 0; i < STATES && lookup[i] != inp; i++ )
    {
        // do nothing
    }

    return static_cast<eUSstate>(i) ;
}

If perhaps you don't want to rely on a brute-force cast and maintaining a look-up table in the same order as the enumerations, then a lookup table having both the string and the matching enum may be used.

eUSstate string_to_enum( std::string inp )
{
    static const int STATES = 50 ;
    struct
    {
        std::string state_string ;
        eUSstate state_enum ;

    } lookup[STATES] { {"AL", AL}, {"AK", AK}, {"AZ", AL} ... } ;

    eUSstate ret = NOSTATE ;
    for( int i = 0; ret == NOSTATE && i < STATES; i++ )
    {
        if( lookup[i].state_string == inp )
        {
            ret = lookup[i].state_enum ;
        }
    }

    return ret ;
}

The look-up can be optimised by taking advantage of alphabetical ordering and performing a binary search, but for 50 states it is hardly worth it.

Upvotes: 0

Slava
Slava

Reputation: 44268

Unfortunately C++ does not provide a portable way to convert enum to string and vice versa. Possible solution would be to populate a std::map<std::string,State> (or hash map) and on conversion do a lookup. You can either populate such map manually or create a simple script that will generate a function in a .cpp file to populate this map and call that script during build process. Your script can generate if/else statements instead of populating map as well.

Another, more difficult, but more flexible and stable solution is to use compiler like llvm and make a plugin that will generate such function based on syntax tree generated by compiler.

Upvotes: 1

Related Questions