80

I want to evaluate a string with a switch but when I read the string entered by the user throws me the following error.

#include<iostream>
using namespace std;

    int main() {
        string a;
        cin>>a;
        switch (string(a)) {
        case "Option 1":
            cout<<"It pressed number 1"<<endl;
            break;
        case "Option 2":
            cout<<"It pressed number 2"<<endl;
            break;
        case "Option 3":
            cout<<"It pressed number 3"<<endl;
            break;
        default:
            cout<<"She put no choice"<<endl;
            break;
        }
        return 0;
    }

error: invalid cast from type 'std::string {aka std::basic_string}' to type 'int

9
  • 2
    std::string doesn't work with switch well. Commented May 5, 2013 at 19:56
  • Switch expressions must evaluate to an integral type. Commented May 5, 2013 at 19:57
  • hash the string if you really want. Some hashing algorithm. Or could you just get the option number, i.e., 1, 2, 3, ...? Commented May 5, 2013 at 19:58
  • if (a >= "Option 1" && a <= "Option 3") {std::cout << "It pressed number " + std::string(a.rbegin(), a.rbegin() + 1) << '\n';} else {std::cout << "She put no choice\n";} Commented May 5, 2013 at 19:59
  • 1
    @antitrust: No, again, try/catch won't protect from UB. You need to test the length with an if before accessing the character. Or were you thinking of the bounds-checked at() method rather than the unchecked operator[]()? Commented May 5, 2013 at 20:14

7 Answers 7

97

As said before, switch can be used only with integer values. So, you just need to convert your "case" values to integer. You can achieve it by using constexpr from c++11, thus some calls of constexpr functions can be calculated in compile time.

something like that...

switch (str2int(s))
{
  case str2int("Value1"):
    break;
  case str2int("Value2"):
    break;
}

where str2int is like (implementation from here):

constexpr unsigned int str2int(const char* str, int h = 0)
{
    return !str[h] ? 5381 : (str2int(str, h+1) * 33) ^ str[h];
}

Another example, the next function can be calculated in compile time:

constexpr int factorial(int n)
{
    return n <= 1 ? 1 : (n * factorial(n-1));
}  

int f5{factorial(5)};
// Compiler will run factorial(5) 
// and f5 will be initialized by this value. 
// so programm instead of wasting time for running function, 
// just will put the precalculated constant to f5 
Sign up to request clarification or add additional context in comments.

11 Comments

The compiler might do so, or might not. If you made f5 constexpr, it would have to.
This is exactly the reason why i hate working with c++. It violates the principle of least surprise in so many ways. This is one of them.
Be careful using only the hash to test for string equality. For example, with this str2int() function, str2int("WS") == str2int("tP") and str2int("5g") == str2int("sa"). See dmytry.blogspot.com/2009/11/horrible-hashes.html As unlikely as a hash collision among the strings you're actually comparing might be, in my opinion it's better to use a lookup table that converts strings to an enum (as suggested by in mskfisher's answer).
str2int(s) should be str2int(s.c_str()) if s is a std::string. A good solution would be to make another str2int function that accept std::strings but isn't constexpr
@schlebe Yes I see, Honestly I cannot say which Hash function is better, the purpose of this answer was to show how to use constexpr for the question. In any case compiler will emit an error if this hash function gives the same result for different strings in switch block.
|
38

You can map the strings to enum values, then switch on the enum:

enum Options {
    Option_Invalid,
    Option1,
    Option2,
    //others...
};

Options resolveOption(string input);

//  ...later...

switch( resolveOption(input) )
{
    case Option1: {
        //...
        break;
    }
    case Option2: {
        //...
        break;
    }
    // handles Option_Invalid and any other missing/unmapped cases
    default: {
        //...
        break;
    }
}

Resolving the enum can be implemented as a series of if checks:

 Options resolveOption(std::string input) {
    if( input == "option1" ) return Option1;
    if( input == "option2" ) return Option2;
    //...
    return Option_Invalid;
 }

Or a map lookup:

 Options resolveOption(std::string input) {
    static const std::map<std::string, Option> optionStrings {
        { "option1", Option1 },
        { "option2", Option2 },
        //...
    };

    auto itr = optionStrings.find(input);
    if( itr != optionStrings.end() ) {
        return itr->second;
    }
    return Option_Invalid; 
}

7 Comments

Is resolveOption another function or is that something in the standard library?
resolveOption is the custom function that calculates the value of the enumerator Options based on the current program input,
For clarity, I've added two possible implementations of a resolveOption() function.
Finally, with this solution, it seems really faster to replace the switch by a if/else statement. In fact, you do that in the first version of the function resolveOption.
Thanks, @coincoin - fixed.
|
21

A switch statement can only be used for integral values, not for values of user-defined type. (And even if it could, your input operation doesn't work, either. The >> operation extracts single tokens, separated by whitespace, so it can never retrieve a value "Option 1".)

You might want this:

#include <string>
#include <iostream>


std::string input;

if (!std::getline(std::cin, input)) { /* error, abort! */ }

if (input == "Option 1")
{
    // ... 
}
else if (input == "Option 2")
{ 
   // ...
}

// etc.

Comments

17

You can only use switch-case on types castable to an int.

You could, however, define a std::map<std::string, std::function> dispatcher and use it like dispatcher[str]() to achieve same effect.

1 Comment

You need to make sure a function exists in the map, otherwise a default-constructed std::function will throw bad_function_call. You can validate the function is initialized with a simple bool check, like auto &fn = dispatcher[str]; if( fn ) { fn(); }
3

what about just have the option number:

#include <iostream>
#include <string>
using namespace std;

int main()
{
    string s;
    int op;

    cin >> s >> op;
    switch (op) {
    case 1: break;
    case 2: break;
    default:
    }

    return 0;
}  

Comments

2

You can't. Full stop.

switch is only for integral types, if you want to branch depending on a string you need to use if/else.

Comments

1

Switch value must have an Integral type. Also, since you know that differenciating character is in position 7, you could switch on a.at(7). But you are not sure the user entered 8 characters. He may as well have done some typing mistake. So you are to surround your switch statement within a Try Catch. Something with this flavour

#include<iostream>
using namespace std;
int main() {
    string a;
    cin>>a;

    try
    {
    switch (a.at(7)) {
    case '1':
        cout<<"It pressed number 1"<<endl;
        break;
    case '2':
        cout<<"It pressed number 2"<<endl;
        break;
    case '3':
        cout<<"It pressed number 3"<<endl;
        break;
    default:
        cout<<"She put no choice"<<endl;
        break;
    }
    catch(...)
    {

    }
    }
    return 0;
}

The default clause in switch statement captures cases when users input is at least 8 characters, but not in {1,2,3}.

Alternatively, you can switch on values in an enum.

EDIT

Fetching 7th character with operator[]() does not perform bounds check, so that behavior would be undefined. we use at() from std::string, which is bounds-checked, as explained here.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.