C++ Part I · Module 3 · Types, Variables, and Initialization
bool and Character Types
Two groups of fundamental types remain, and they are the two least like the rest. One holds a single logical answer and nothing else, and it is genuinely its own type rather than a small integer dressed up — though it converts to one readily enough that people conclude otherwise. The other is the type C++ uses for characters, and it is more interesting than it looks: it is an integer type that happens to be used for text, it comes in three separate flavours that are genuinely distinct types, and one of those three has a property that varies between platforms and quietly breaks code written by people who assumed it did not. This chapter covers both, and it connects a loose end from Chapter 3.4 — the surprise where a std::uint8_t printed as a letter turns out to have its explanation here.
Foundation14 min readC++Part IFundamentalsTypes
Part I · Module 3 · Chapter 3.6 · bool and Character Types
1. Two Groups, One Chapter
bool and the character types are grouped together here for a practical reason rather than a conceptual one: each is small, each is short to teach, and each has exactly one property that causes real trouble.
For bool, the trouble is people treating it as an integer.
For char, the trouble is people assuming it is signed.
2. bool Is a Logical Type
bool parity_error = false;
bool link_up = true;A bool has exactly two values: true and false. They are keywords, part of the language, and they are the only values a bool can hold.
That is worth saying plainly because of what bool is not:
boolis its own type. It is not a small integer that happens to be restricted to 0 and 1.
The distinction matters because the type is what tells the compiler, and the reader, what kind of question is being answered. parity_error answers a yes/no question. A type that says so is better than an int that holds 0 or 1 by convention, because the convention is not checked and the type is.
Measured on the machine used for this chapter, sizeof(bool) is 1. That is not one bit — Chapter 3.2 §4 explained why there is no type smaller than one char. A bool occupies a whole addressable unit and carries one bit of information.
3. bool Converts to and from Numbers
C++ does convert between bool and the numeric types, and you will see it, so here is what happens:
#include <iostream>
int main()
{
bool link_up = true;
bool idle = false;
std::cout << "link_up = " << link_up << '\n';
std::cout << "idle = " << idle << '\n';
return 0;
}Expected output:
link_up = 1
idle = 0Going the other way, any non-zero number converts to true and zero converts to false.
Two notes, each with an owner elsewhere:
- Printing
1and0rather than the words is a formatting default.std::boolalphachanges it, and Chapter 23.2 owns stream formatting. Chapter 2.8 §3 flagged this when you first met it. - Where these conversions happen automatically, and the rules governing them, is Chapter 4.10.
The conversions existing does not make bool an integer. An int converts to a double too, and nobody concludes they are the same type. A conversion is a defined relationship between two types, which is the opposite of them being one type.
4. char Is an Integer Type Used for Text
Now the more interesting half.
char status_letter = 'A';Single quotes mean one character — Chapter 2.2 §5. What is stored is a number: the code that represents that character in the implementation's character set.
#include <iostream>
int main()
{
char status_letter = 'A';
std::cout << "as a character = " << status_letter << '\n';
std::cout << "as a number = " << static_cast<int>(status_letter) << '\n';
return 0;
}Expected output:
as a character = A
as a number = 65The same object, read two ways. This is Chapter 3.1 §5 once more: the storage holds a bit pattern, and what you get depends on the type through which you read it. Sent to a stream as a char, it prints the character. Converted to int first, it prints the code.
So char is an integer type. You can do arithmetic on it. What makes it special is that the language and the standard library treat it as the unit of text, and that sizeof(char) is 1 by definition (Chapter 3.2 §3).
5. Three Character Types, Not One
Here is the fact most treatments skip, and without it the next section makes no sense.
C++ has three distinct narrow character types:
char c; // the text type
signed char sc; // a signed 8-bit-or-wider integer
unsigned char uc; // an unsigned oneThey are three separate types. Not two with an alias:
#include <iostream>
#include <type_traits>
int main()
{
std::cout << "char is the same type as signed char? "
<< std::is_same<char, signed char>::value << '\n';
std::cout << "char is the same type as unsigned char? "
<< std::is_same<char, unsigned char>::value << '\n';
return 0;
}Expected output:
char is the same type as signed char? 0
char is the same type as unsigned char? 0Both zero. std::is_same answers a compile-time question about whether two types are the same type, and the answer is no in both cases. Plain char is a third type, distinct from both of the others, even though it must behave like one of them for storage and range.
All three are the same size — measured here, 1 each. The difference is in how the bits are interpreted, and in the fact that char is a separate type for the purposes of overload resolution and template matching, which you will meet from Module 6 onward.
6. Plain char Has Implementation-Defined Signedness
This is the property that catches people.
Whether plain
charis signed or unsigned is implementation-defined. The standard does not say. It must match one of the other two, and which one is your platform's choice.
Measure it:
#include <climits>
#include <iostream>
int main()
{
std::cout << "CHAR_MIN = " << CHAR_MIN << '\n';
std::cout << "plain char is "
<< (CHAR_MIN < 0 ? "signed" : "unsigned")
<< " on this implementation\n";
return 0;
}Example output on Apple clang 21, arm64 macOS:
CHAR_MIN = -128
plain char is signed on this implementationThat is a measurement of one platform, not a C++ fact — the discipline from Chapter 3.2 §4. Plain char is signed on x86 Linux, macOS and Windows, and unsigned on several ARM Linux configurations. Both are fully conforming.
Why it matters: a char holding a byte value above 127 is a positive number on one platform and a negative number on another, from the same source file. Any comparison, any arithmetic, any use as an index then behaves differently — and nothing warns you, because both builds are correct C++.
7. The Loose End from Chapter 3.4
Chapter 3.4 §6 showed a surprise and promised the reason here:
std::uint8_t error_count = 65;
std::cout << error_count << '\n'; // prints A, not 65Now it is explicable. std::uint8_t is required to be exactly 8 bits unsigned, and on mainstream implementations the type that fits is unsigned char — a character type. When you send a character type to a stream, the stream prints the character.
So the behaviour is not a quirk of the fixed-width types at all. It is this chapter's subject arriving one chapter early: std::uint8_t is a character type wearing a numeric name, and the stream treats it accordingly. The fix from 3.4 — convert to unsigned before printing — is the right one, and now you know why it is needed.
8. The Wider Character Types
C++ has more character types than the three narrow ones, for text that does not fit in a byte:
wchar_t wide; // width is implementation-defined
char16_t utf16; // for UTF-16 code units
char32_t utf32; // for UTF-32 code unitsThat is all this chapter says about them, and the restraint is deliberate. Text encoding is a real subject with real depth — what a code unit is, how multi-byte encodings work, why the number of characters in a string is not the number of bytes. Module 9 covers strings and is where it belongs.
For the work this curriculum targets, the narrow types carry almost everything: char for ASCII text in logs and messages, unsigned char or std::uint8_t for binary data.
9. Mistakes Worth Making Once
Using int for a yes/no answer. Nothing checks that it holds only 0 or 1, and a reader cannot tell whether 1 means "yes" or "one". Use bool.
Concluding bool is an integer because it converts to one. int converts to double too. A conversion is a relationship between two types, not evidence they are one type.
Expecting true to print as the word. It prints 1 by default. std::boolalpha changes that, in Chapter 23.2.
Assuming plain char is signed. It is on this machine and on x86 platforms; it is unsigned on some ARM Linux configurations. The standard does not say.
Storing binary data in a plain char. A payload byte above 127 is positive on one platform and negative on another. Use unsigned char or std::uint8_t.
Thinking char and signed char are the same type. Measured: they are not. Plain char is a third, distinct type.
Printing a std::uint8_t and getting a letter. It is a character type underneath. Convert to unsigned first — Chapter 3.4 §6.
10. What You Should Be Able to Say Now
boolis its own type with exactly two values,trueandfalse— not a small integer.sizeof(bool)is 1 here, which is onechar, not one bit. There is no type smaller thanchar.boolconverts to and from numbers:trueis 1,falseis 0, and any non-zero value converts totrue. The conversions do not make it an integer.- Printing
1/0rather than words is a formatting default;std::boolalphais Chapter 23.2. - Use
boolfor a yes/no answer. Anintholding 0 or 1 relies on an unchecked convention. charis an integer type used for text. Measured:'A'prints asAthrough acharand as65through anint.- There are three distinct narrow character types:
char,signed charandunsigned char. Verified withstd::is_same— plaincharis a third type, not an alias for either. - Whether plain
charis signed is implementation-defined. MeasuredCHAR_MIN = -128, so it is signed here; it is unsigned on some ARM Linux builds. Both conform. - The consequence: a byte above 127 in a
charis positive on one platform and negative on another, with no warning. - Use
charfor text,unsigned char(orstd::uint8_t) for raw bytes,signed charrarely. std::uint8_tprints as a letter because it isunsigned charon mainstream implementations — a character type.wchar_t,char16_tandchar32_texist for wider text; Module 9 covers encoding.
11. Exercises
Exercise 1 — predict the output. Say what each line prints and why:
bool link_up = true;
std::cout << link_up << '\n';
std::cout << (link_up + 1) << '\n';Exercise 2 — choose the type. For each, pick bool, char, unsigned char or std::uint8_t, with one phrase of justification: (a) whether a parity check failed; (b) a letter in a status message; (c) one byte of packet payload; (d) a register's 8-bit value; (e) the number of errors found.
Exercise 3 — the platform split. A program stores packet bytes in a char array and compares each against 200. Describe what happens on a platform where char is signed versus one where it is unsigned, say whether either compiler is wrong, and give the fix.
Exercise 4 — three types. std::is_same<char, signed char>::value is 0. Explain what that tells you, and why it is surprising given that char must behave like one of the other two.
Exercise 5 — explain the letter. Without re-reading Chapter 3.4, explain why std::cout << std::uint8_t{65} prints A. Your answer should name the underlying type.
Exercise 6 — argue for bool. A colleague uses int ok = 1; and says it is clearer because it can later hold an error code. Give the specific objection, and say what they should write if they genuinely need more than two outcomes.
Exercise 7 — guarantee or measurement? Classify each: (a) sizeof(char) == 1; (b) CHAR_MIN == -128; (c) char, signed char and unsigned char are three distinct types; (d) true converts to 1.
12. Summary
boolis a logical type with two values. It converts to and from numbers; that does not make it an integer.sizeof(bool)is 1 — onechar, not one bit.- Use
boolfor yes/no. Anintholding 0 or 1 is an unchecked convention. charis an integer type used for text. The same object prints asAor65depending on the type you read it through.- Three distinct narrow character types, verified: plain
charis neithersigned charnorunsigned char. - Plain
char's signedness is implementation-defined. Measured signed here; unsigned on some ARM Linux builds. Both conform. charfor text,unsigned charorstd::uint8_tfor raw bytes,signed charrarely. Follow this and the signedness question never arises.std::uint8_tprints as a letter because it isunsigned charunderneath — Chapter 3.4's surprise, explained.- Wider character types exist; encoding belongs to Module 9.
Every type in this module has now been introduced, and every example has quietly done the same thing: given the object a value on the line that declared it. That looks like one operation with one obvious spelling, and it is not. C++ has several distinct ways to give an object its initial value, they do not all mean the same thing, and one of them will reject a conversion that the others accept without a word. Chapter 3.7 takes them apart — and it is the chapter in this module whose habits will show up in every line of C++ you write afterwards.
Standards & specifications
- Governing standard
- ISO/IEC 14882 (the C++ language standard)(opens ISO/IEC (JTC 1/SC 22/WG 21) in a new tab)
Defines the C++ language and its standard library — the type system and templates, the object model and object lifetime, initialisation and overload resolution, the memory model and the containers, algorithms and utilities the library provides. Like C's standard it is a FAMILY of editions (C++98/03, C++11, C++14, C++17, C++20, C++23) and the editions are NOT interchangeable: a feature this curriculum relies on may be unavailable, differently specified or merely proposed in another edition, so each lesson names the edition its claim depends on. It does not define any foreign-function interface to a hardware description language, nor what a compiler actually emits, nor platform behaviour on real silicon — those are the implementation's and the device's contracts. Working drafts published by WG21 are freely available and are what most engineers read day to day; a draft is not the published standard.
This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.
Where this fits
Part of the C++ Programming curriculum.
