Embercode
✕
C++ ConditionalsLesson 9 of 14

35 min read

Switch on strings and enums

Why this matters

A program often has to choose one action from several options. A menu might map the word "start" to one function and "stop" to another. A traffic light might have three states and each state needs its own message. In C++ the tool for this job is the switch statement. It looks at one value and jumps to the matching case label.

Many beginners try to write a switch on a string, the way they would write it on a number. C++ does not allow that. The compiler stops with an error. This lesson shows what switch can and cannot check, and three ways to write the same choice on text.

The if and else if form and the switch form are both covered in the earlier lessons of this module, so this lesson does not repeat their basic rules.

The idea: switch needs a value it can compare exactly

A switch statement works on a value that is stored as a whole number. That covers int, char, bool, and enumerations. An enumeration, which the lesson calls an enum, is a named list of options that you define yourself. The compiler compares the value with each case label and jumps to the first match.

A std::string is not a whole number. It is a sequence of characters, and its size can change while the program runs. The compiler has no single number to compare with each label, so it refuses to build the switch.

The rule is short. If the value is an integer, a char, a bool or an enum, a switch can check it. If the value is text, use one of the three forms below.

The table shows the values that come up most often in beginner programs and what the compiler does with each one in a switch. The double and std::string rows were checked with g++ 15 and -std=c++20.

Type of the valueWorks in a switch?
int, char, boolYes
enum (enum class)Yes, with names as the case labels
doubleNo, the error is "switch quantity not an integer"
std::stringNo, the same error

Worked example 1: the same choice with an if chain

For text, the if chain is the simplest form. Each branch compares the string with ==. The == operator for std::string compares the characters, so "apple" matches only the word apple.

The function below returns the price of an item. It returns -1 when the item is not in the list.

C++ · editable

The output is:

apple: 30
bread: 45
tea: -1

The string is passed as const string&. The & means the function receives the caller's string without making a copy. The const means the function does not change it. The last return -1 is the fallback for an unknown item. Keep a fallback in every chain, so that an unexpected value does not leave the function without an answer.

The if chain works for any number of options. Its cost is that each == is checked one after another. For three or four options this does not matter. For a long list, the map form in worked example 3 is more direct.

Worked example 2: switch on an enum

An enum is the right tool when the set of options is fixed and known ahead of time. Each option gets a name, and the switch can check every name. Use enum class so that the names belong to the enum and do not leak into the rest of the file.

C++ · editable

The output is:

stop
slow down
go

Each case names one value of the enum. Each branch returns before the next label can run, so no break is needed here. The return "unknown"; after the switch is reached only if the value matches none of the three names, which takes a cast such as static_cast<Light>(7), and it is a safe fallback.

The function returns a std::string_view. A string view is a read-only window onto text that already exists. The text here is a string literal, which lives for the whole program, so returning a view of it is safe. Returning a view of a temporary string would leave the view pointing at memory that is already gone.

The compiler also checks completeness. If one enumerator is missing from the switch, g++ reports it when -Wall is on. Here is the warning from the action function above with the Yellow case deleted, saved as missing.cpp.

missing.cpp:8:12: warning: enumeration value ‘Yellow’ not handled in switch [-Wswitch]

When you add a fourth light later, this warning tells you every switch that needs a new case.

Worked example 3: a map from names to actions

A std::map stores pairs of a key and a value, and it finds a key quickly. A map can replace a long chain. The key is the text you would have compared. The value is the answer, or a function that does the work.

The example below stores a function for each operation name. A function that is stored in a map is an ordinary value, so the map can hold it like any number.

C++ · editable

The output is:

add 6 3 = 9
mul 6 3 = 18
div: unknown operation

Three details matter here.

  • The find call returns an iterator. An iterator is a position in the map. If the key is missing, the position is operations.end(), which means "past the last item". The code checks for that before it uses the result.
  • found->second is the value that belongs to the key. The arrow reads the value from the pair that the iterator points to. The first field is the key.
  • The lambda [](int a, int b) { return a + b; } is a small unnamed function. The brackets at the front say that it does not capture any outside variables.

Adding a new operation means adding one line to the map. No case label or if branch has to be changed in other places.

Worked example 4: text in, enum out, then a switch

Real programs often receive text from a user or a file and then need a switch. The clean way is to turn the text into an enum once, at the edge of the program, and then use a switch on the enum everywhere else. The rest of the program then works with names that the compiler can check.

The function parse below does the first step. It looks up the word in a map and returns a Command value. A word that is not in the map becomes Command::Unknown.

C++ · editable

The output is:

start: starting
stop: stopping
pause: not a command

The static keyword on the map means the map is built once, the first time parse runs, and then kept. Without it, the map would be rebuilt on every call. The Command::Unknown value handles every word the map does not list, so the switch covers all three possible results. If someone adds a new command later, the compiler warns about each switch that does not handle it.

Worked example 5: reading lines of text

Text often comes one line at a time, from the keyboard or from a file. The function std::getline reads one line and stores it in a string. It returns false when there are no more lines, which lets a while loop stop at the end.

To keep the example the same on every run, the input below is a std::istringstream, which is a stream made from text that is already in the program. A real program would read from cin or from a file, and the rest of the code would not change.

C++ · editable

The output is:

starting
stopping
not a command: pause

This version of parse uses plain if statements instead of a map. Both versions are correct. For three words, the if form is shorter to read. The map form is easier to extend, because each new word is one line in the table and no new if is needed.

The text inside "start\nstop\npause\n" contains \n in three places. Each \n is a newline character, which ends one line and starts the next. Without them, the three words would be one line.

Choosing a form

Use this table to choose among the forms in this lesson.

FormGood whenWatch out for
switch on an enumThe options are fixed and known when you write the codeThe value must already be an enum, so text needs a conversion first
if chain with ==There are only a few options, or each test is differentEvery new option adds another branch to read
std::map lookupThe options are data, such as names from a file, or there are many of themUse find, not [], when a missing key is possible

Common mistakes

Mistake 1: switching on a string

Here is a program that tries to switch on a string.

#include <iostream>
#include <string>
using namespace std;
 
int main() {
    string command = "stop";
    switch (command) {
        case "stop":
            cout << "stopping\n";
            break;
    }
}

Saved as casestr.cpp, g++ rejects it with this error.

casestr.cpp:7:13: error: switch quantity not an integer
    7 |     switch (command) {
      |             ^~~~~~~

The message "switch quantity not an integer" means the value in the parentheses is not a whole number. Change the code to one of the three forms above.

Mistake 2: using an int for an enum

With enum class, the names are the only allowed values. A plain number does not convert to the enum type, so a case 0: label is rejected.

#include <iostream>
using namespace std;
 
enum class Light { Red, Yellow, Green };
 
int main() {
    Light light = Light::Red;
    switch (light) {
        case 0:
            cout << "stop\n";
            break;
    }
}

Saved as enumint.cpp, the error with g++ 15 is this.

enumint.cpp:9:14: error: could not convert ‘0’ from ‘int’ to ‘Light’
    9 |         case 0:
      |              ^

Use the name, such as Light::Red, in every case. The name is clearer for the reader, and it still works if the numbers behind the names change.

Mistake 3: indexing a map with a key that is missing

The [] operator on a std::map creates a new entry with a zero value when the key is missing. The map then has one more item than before. The program does not report an error, and the new entry stays in the map.

#include <iostream>
#include <map>
#include <string>
using namespace std;
 
int main() {
    map<string, int> prices = {{"apple", 30}};
    cout << "size before: " << prices.size() << "\n";
    cout << "tea price via []: " << prices["tea"] << "\n";
    cout << "size after: " << prices.size() << "\n";
}

The output is:

size before: 1
tea price via []: 0
size after: 2

The price of tea was never set, but the program printed 0 and the map now holds tea. To read a key without changing the map, use find, as in worked example 3. The at function is the other safe choice. It throws an exception when the key is missing. An exception is an error that stops the normal flow of the function and jumps to a catch block.

try {
    cout << prices.at("coffee") << "\n";
} catch (const out_of_range& e) {
    cout << "caught: " << e.what() << "\n";
}

With a map that has no "coffee" key, g++ on Linux prints caught: map::at, and other standard libraries may word it differently. The at function does not add anything to the map.

Mistake 4: missing an enum case

If you leave a value out of an enum switch, the code still compiles, and the missing branch does nothing. The warning from -Wall is the only sign of the problem. Read every -Wswitch warning and add the missing case, or handle it with a default: label on purpose.

Mistake 5: comparing a character array with ==

A string literal such as "stop" is stored as a fixed array of characters. A char array variable is also an array. The == operator does not compare the letters of two arrays. It compares where they are stored in memory, so two arrays with the same text are not equal. The program below was saved as cstr.cpp.

C++ · editable

g++ 15 compiles it, but reports this warning under -Wall.

cstr.cpp:7:54: warning: comparison with string literal results in unspecified behavior [-Waddress]
    7 |     cout << "command == \"stop\" gives " << (command == "stop") << "\n";
      |                                              ~~~~~~~~^~~~~~~~~

The output is:

command == "stop" gives 0
strcmp gives equal: 1

The strcmp function compares the letters and returns 0 when they are the same. The std::string type avoids the problem, because its == compares the letters. This is one more reason to use std::string for text in a switch replacement.

Practice

Exercise 1

Write a function int days_in(const string& month) that returns 31 for "january", 30 for "april", and 28 for "february". For any other text it returns -1. Use an if chain.

Show the answer

C++ · editable

The output is:

january: 31
april: 30
february: 28
june: -1

Exercise 2

Write an enum class Suit with the values Hearts, Spades, Clubs and Diamonds. Then write a function char symbol(Suit s) that returns 'H', 'S', 'C' or 'D' with a switch. Do not add a default label.

Show the answer

#include <iostream>
using namespace std;
 
enum class Suit { Hearts, Spades, Clubs, Diamonds };
 
char symbol(Suit s) {
    switch (s) {
        case Suit::Hearts:
            return 'H';
        case Suit::Spades:
            return 'S';
        case Suit::Clubs:
            return 'C';
        case Suit::Diamonds:
            return 'D';
    }
    return '?';
}
 
int main() {
    cout << symbol(Suit::Clubs) << symbol(Suit::Diamonds) << "\n";
}

The output is CD. Every value is covered, so -Wall does not warn. The return '?'; after the switch is a fallback that the compiler accepts.

Exercise 3

Change worked example 3 so that it also knows the operation "div", which divides the first number by the second. Do not change main.

Show the answer

Add one line to the map.

{"div", [](int a, int b) { return a / b; }},

With the same main, the output for div is div 6 3 = 2. The map key is the name the user types, and the lambda is the code that runs for it.

Exercise 4

The function below is meant to return the number of wheels for a vehicle type. Find the mistake and explain what the program prints.

#include <iostream>
#include <map>
#include <string>
using namespace std;
 
int main() {
    map<string, int> wheels = {{"car", 4}, {"bike", 2}};
    cout << wheels["boat"] << "\n";
    cout << wheels.size() << "\n";
}

Show the answer

The program prints 0 and then 3. The [] operator on a map adds the missing key "boat" with the value 0, so the size grows to 3. The fix is to use find or at, which do not change the map. With find, the program checks for wheels.end() before it reads the value.

Exercise 5

Write a switch on a char that prints vowel for a, e, i, o and u, and other for every other character. Use the fact that a case label can list several values one after another.

Show the answer

C++ · editable

The output is:

a: vowel
k: other
u: vowel

The labels 'a' through 'u' have no break between them, so they all run the same branch. The default label covers every other character.

Summary

  • A switch works on integers, char, bool and enums, but not on std::string.
  • For text, use an if chain with ==, or a std::map lookup with find.
  • For a fixed set of names, use enum class and a switch, and read every -Wswitch warning.
  • Do not use [] to read a map when the key may be missing, because it adds the key.
Practice this topic: coding problems for C++ Conditionals →

Stuck? Ask the tutor

Ask about anything in this lesson. Answers are short and use simple words.

Sign in to ask the tutor.

← Range checks without chained comparisons