79

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

Mat
  • 202,337
  • 40
  • 393
  • 406
Alejandro Caro
  • 998
  • 1
  • 10
  • 22
  • 2
    `std::string` doesn't work with switch well. – David G May 05 '13 at 19:56
  • Switch expressions must evaluate to an integral type. – juanchopanza May 05 '13 at 19:57
  • [hash the string](http://stackoverflow.com/questions/2535284/how-can-i-hash-a-string-to-an-int-using-c) if you really want. [Some hashing algorithm](http://www.cse.yorku.ca/~oz/hash.html). Or could you just get the option number, i.e., 1, 2, 3, ...? – gongzhitaao May 05 '13 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";}` – chris May 05 '13 at 19:59
  • is it possible to switch on the 7th character, and surround with try/catch to test for UB in case the string does not have 7 characters ? – kiriloff May 05 '13 at 20:12
  • 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[]()`? – syam May 05 '13 at 20:14
  • @syam it seems to work with `at()` according to your comment and http://www.cplusplus.com/reference/string/string/at/ so i will post my solution. thanks! – kiriloff May 05 '13 at 20:35
  • http://coliru.stacked-crooked.com/a/8e62ff41637776b9 (see main for usage) – Mooing Duck Apr 17 '16 at 00:47
  • A switch is basically a strongly typed if/elseif for int-checks. There's no reason for its use to ever be a _requirement_ if someone gives you flak for it. That being said, I wish there was sugar that basically allowed you to write switches using non-ints that the compiler converted into if/elseif checks. The readability is my preference for less verbose operations that have many optional triggers. – kayleeFrye_onDeck Jul 10 '17 at 20:29

7 Answers7

95

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 
Community
  • 1
  • 1
Serhiy
  • 1,332
  • 1
  • 16
  • 24
  • 2
    The compiler might do so, or might not. If you made `f5` `constexpr`, it would have to. – Deduplicator Oct 28 '14 at 20:38
  • 24
    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. – Richard Apr 12 '15 at 17:06
  • Excellent! Very helpful – Fred Sep 17 '15 at 20:23
  • I like the technique, but I don't necessarily like the functions chosen since `str2int(s)` is going to have to be calculated at runtime. For strings in particular, a FNV hash is extremely fast to calculate at runtime and a constexpr version is available on google for generating the switch constants. – Chuu Dec 28 '15 at 17:50
  • 21
    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 http://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). – Michael Burr Mar 24 '16 at 23:38
  • 1
    `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::string`s but isn't constexpr – Winter Jan 18 '17 at 14:14
  • nice to see djb hash + constexpr. but there might be a collision and error shown will be very misleading. – Nick Aug 01 '17 at 12:49
  • Your HASH function is not the same that what I can see if I follow your link HERE !ndeed, in your hash function you use a ^ operator to make an XOR of INT but in HERE, it is a + operator for 2 or 3 answers and a ^ operator for only one !!! What is correct HASH algorithm to use ? – schlebe Feb 13 '19 at 08:15
  • 1
    @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. – Serhiy Feb 13 '19 at 08:52
  • 1
    @Serhiy: for your information, Dan Bernstein seems to be author of this algorithm and he has defined his algorithm using first + operator and in second time ^ operator. I have found some information there: www.cse.yorku.ca/~oz/hash.html. But it is a good thing that compiler verify that possible values are all distincts. The only disturbing issue is that value to evaluated can be equal to hash value without being equal to String !!! – schlebe Feb 13 '19 at 09:38
36

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; 
}
mskfisher
  • 3,291
  • 4
  • 35
  • 48
  • Is resolveOption another function or is that something in the standard library? – Rob Rose Oct 12 '16 at 02:02
  • resolveOption is the custom function that calculates the value of the enumerator Options based on the current program input, – LastBlow Nov 29 '17 at 07:22
  • For clarity, I've added two possible implementations of a `resolveOption()` function. – mskfisher Feb 08 '18 at 20:07
  • 3
    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. –  Jun 19 '20 at 14:12
  • Agreed. The switch on enum is faster (a single integer comparison) but the conversion overhead only makes sense if you need to do the comparison over and over. – mskfisher Jun 19 '20 at 16:25
  • For the map lookup I think it should be return itr->second; – coincoin Oct 19 '21 at 15:10
  • 1
    Thanks, @coincoin - fixed. – mskfisher Oct 19 '21 at 15:17
19

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.
John Kugelman
  • 349,597
  • 67
  • 533
  • 578
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
16

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.

rmn
  • 2,386
  • 1
  • 14
  • 21
  • 2
    You need to make sure a function exists in the map, otherwise [a default-constructed `std::function` will throw `bad_function_call`](http://stackoverflow.com/a/7527115/44390). You can validate the function is initialized with a simple bool check, like `auto &fn = dispatcher[str]; if( fn ) { fn(); }` – mskfisher May 01 '15 at 13:19
3

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.

syam
  • 14,701
  • 3
  • 41
  • 65
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;
}  
gongzhitaao
  • 6,566
  • 3
  • 36
  • 44
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.

kiriloff
  • 25,609
  • 37
  • 148
  • 229