Dataslope logoDataslope

Types and Values

How C++ thinks about types — categories, conversions, and why static typing is your friend.

In the previous chapter we said every variable has a type, and a type is a promise about memory and operations. Now let's zoom out and look at the C++ type system as a whole. The goal is not to memorize every category but to build a map of the territory so you recognize where every type fits.

Why types?

A type system answers three questions:

  1. What can I store? A bool can store true or false; a std::string can store any sequence of bytes.
  2. What can I do with it? You can << a std::string to std::cout; you cannot << a raw int[5] array.
  3. What can be converted to what, and how dangerous is each conversion?

A static type system answers all three at compile time. Bugs caught by the compiler are bugs your users never see.

A map of C++ types

C++ types break down into a small family of categories:

Most of your day will be spent with int, double, bool, std::string, std::vector, and your own classes — but it helps to know the rest exists.

Integral types in detail

C++ integers come in several widths and two signedness flavors:

TypeBitsRange (signed)Range (unsigned)
char / signed char / unsigned char8-128..1270..255
short16-32 768..32 7670..65 535
int32 (typical)±2.1B0..4.29B
long32 or 64variesvaries
long long64±9.2 quintillion0..1.8E19
std::size_t32 or 64n/a (unsigned)size of any object

std::size_t is the type returned by sizeof and used for sizes and array indices. It is unsigned, which leads to a classic trap:

Code Block
C++ 20 (202002L)

A good habit: use signed integer types for arithmetic and only use std::size_t when you really need to interoperate with a size or index from the standard library.

Floating-point types and their surprises

float and double represent real numbers, but only approximately. The hardware uses IEEE-754 binary representation, which cannot exactly store many "nice" decimal numbers.

Code Block
C++ 20 (202002L)

0.1 + 0.2 is not exactly 0.3 in binary floating-point. Never compare floats with ==; compare with a tolerance: std::abs(a - b) < 1e-9. Use int (or long long) for money, counts, and anything that needs to be exact.

Compound types preview

We will cover each of these properly in later chapters; for now you just need to recognize their shape.

int n;           // a value
int* p;          // a pointer to an int
int& r = n;      // a reference to an int
int a[5];        // an array of 5 ints
struct Point { int x; int y; };  // a struct (aggregate type)
enum class Color { Red, Green, Blue };  // a scoped enum

A pointer holds a memory address. A reference is an alias for another name. An array is a fixed-size block of consecutive values. A struct bundles several values into one. An enum class is a small set of named constants with its own type.

Type conversions

C++ has two big categories of conversions:

  1. Implicit (silent) conversions. The compiler inserts them for you when there is a "standard" way to convert. Examples: int -> double, int -> bool (zero is false, nonzero is true).
  2. Explicit conversions. You write them yourself. Modern C++ prefers the named casts: static_cast, const_cast, reinterpret_cast, dynamic_cast.
Code Block
C++ 20 (202002L)

Use static_cast when you actually mean it. C-style casts ((int)3.9) work but are easy to grep-miss and easy to over-do (they will silently strip const, perform reinterpretation, or do both).

Truthiness

In conditions like if (x), C++ treats any non-zero value as "true" and zero as "false." This includes pointers (a null pointer is false; any non-null pointer is true).

Code Block
C++ 20 (202002L)

For clarity in real code, prefer explicit comparisons (if (n != 0), if (p != nullptr)).

Named constants and constexpr

For values that are truly constant and known at compile time, use constexpr rather than const. constexpr guarantees the value is computed at compile time and can be used wherever a compile-time constant is needed (e.g. as an array size).

Code Block
C++ 20 (202002L)

Prefer constexpr over preprocessor #define MAX 100 macros. The constexpr version has a real type, respects scope, and the compiler can reason about it.

Enumerations: a small set of named values

When a variable should hold one of a small fixed set of options, use a enum class:

Code Block
C++ 20 (202002L)

Why not just use int 0/1/2? Because then nothing prevents you from writing l = 7; or comparing two unrelated enums. With enum class, the type system catches misuse for free.

Type aliases

Long type names get repetitive. using declares a friendly alias:

using StringMap = std::map<std::string, std::string>;

StringMap config;   // much shorter than spelling std::map<...> again

Aliases are just names — they don't create new types. But they make code dramatically more readable.

Reading C++ types right to left

When you see a complex type, read from the variable name outward.

const int* p;     // p is a pointer to const int
int* const p;     // p is a const pointer to int
const int* const p;   // p is a const pointer to const int

The trick: the word const qualifies whatever is to its left (or, if there is nothing to its left, whatever is to its right). Practice this on small examples; the same pattern scales up.

Challenge: choose the right type

Challenge
C++ 20 (202002L)
Average of a vector

Implement double average(const std::vector<int>& xs) that returns the mean of xs. Pay attention to the types: xs.size() is std::size_t and the sum is int — you'll need a cast or careful arithmetic to get the right double result. The fixed input prints exactly 3 on success.

Test your understanding

QuestionSelect one

Why is it dangerous to compare double values with ==?

The CPU does not support double equality at all.

The compiler always emits a warning, so the operation is forbidden.

Floating-point arithmetic is approximate; many decimal values cannot be represented exactly in binary, so the comparison may fail even for inputs that look mathematically equal.

It is dangerous only on 32-bit systems.

QuestionSelect one

What's the safer modern alternative to a C-style cast like (int)3.9?

A reinterpret_cast.

A static_cast, e.g. static_cast<int>(3.9).

A const_cast.

A dynamic_cast.

QuestionSelect one

What is the main reason to prefer enum class Color { ... } over plain integers for representing a small fixed set of options?

It is faster at runtime.

It uses less memory.

It gives the values a distinct type, so the compiler can prevent misuse like mixing colors with unrelated integers.

It is required by the C++ standard.

QuestionSelect one

When should you reach for constexpr instead of const?

Never; const is always better.

Whenever you want a variable that might change later.

When the value is genuinely known at compile time and you may need it in a context that requires a compile-time constant (array sizes, template arguments, etc.).

Only inside template metaprogramming code.

Next: how to control the flow of a program — if, while, for, switch, and how to think about them as decisions and loops.

On this page