Why there is no toupper for strings
In many languages a string has a method such as upper() or toUpperCase(). The C++ std::string has no such member. What the standard library gives you instead is a function for a single character, toupper, with its partner tolower, and a general algorithm, transform, that applies a function to every element of a range. Together they convert a whole string.
Single characters
The two character functions live in <cctype>:
char a = toupper('q'); // 'Q'
char b = tolower('Q'); // 'q'
char c = toupper('7'); // '7', unchanged
Letters change case, and any other character comes back as it was. Despite the name, they take and return an int, a leftover from the C library, which is why you may see a cast when the result goes back into a char.
Applying a function to a whole string
transform comes from <algorithm> and takes four arguments:
- where to start reading,
word.begin(); - where to stop,
word.end(); - where to start writing the results;
- the function to apply to each element.
If the output starts at the same place as the input, the string is overwritten in place. That is what the lesson does:
string s = "Hello World";
string upper = s;
transform(upper.begin(), upper.end(), upper.begin(), ::toupper);
// upper is "HELLO WORLD"
string lower = s;
transform(lower.begin(), lower.end(), lower.begin(), ::tolower);
// lower is "hello world"
The leading :: in ::toupper tells the compiler to use the global function from the C library. It matters because the C++ library also has a two-argument toupper for locales in <locale>. A plain toupper can then be ambiguous when you pass the function to transform, and the compiler cannot tell which one you mean.
Why the copy comes first
transform writes into the string, so it changes whatever it is pointed at. If you transformed s directly, the original text would be lost. Copying first, as in string upper = s;, leaves s intact, and you can make both an uppercase and a lowercase version from it. Assigning one string to another copies the characters.
A safer version for unusual text
The character functions expect a value that fits in an unsigned char (or equals EOF). Plain char may be signed, and a character outside the ASCII range, such as part of an accented letter in UTF-8, can then be negative, which is undefined behaviour. For ASCII text such as the lesson's, ::toupper is fine. For text you do not control, wrap the call in a lambda that converts first:
transform(s.begin(), s.end(), s.begin(),
[](unsigned char c) { return toupper(c); });
Keep in mind that these functions handle one byte at a time. They convert the English letters A to Z in the default "C" locale, but they cannot do proper case conversion for text in other alphabets.
The exercise, step by step
The editor starts with string word = "Hello World";. The task is to produce an uppercase and a lowercase version.
- Copy for the uppercase version. Declare a
stringnamedupperand initialize it fromword. - Uppercase it in place. Call
transformover the whole ofupper, writing the output back toupper.begin(), with::toupperas the function. - Copy again for the lowercase version. Declare
lowerfromword. Start fromword, not fromupper, so that this copy has the original letters. - Lowercase it the same way, with
::tolower.
string word = "Hello World";
string upper = word;
transform(upper.begin(), upper.end(), upper.begin(), ::toupper);
string lower = word;
transform(lower.begin(), lower.end(), lower.begin(), ::tolower);
Afterwards upper is "HELLO WORLD", lower is "hello world", and word is still "Hello World". Spaces and any digits or punctuation pass through unchanged.
Common mistakes
- Calling
word.toupper()orword.upper(). Strings have no such members. - Passing
toupper()with parentheses.transformneeds the function itself, not its result. - Transforming the original and then expecting to still have it.
- Writing the output at the wrong place. The third argument must be where the results go. Using
upper.end()there writes past the string. - Forgetting the headers.
transformneeds<algorithm>and the character functions need<cctype>.
Key takeaways
- Convert a string by applying
toupperortolowerto every character withtransform. - Copy the string first if you need the original.
- For non-ASCII input, cast to
unsigned charbefore calling the character functions.