Constants, Literals, and Strings Recap

This chapter covered two themes that turn out to be closely related: values that must not change, and text. The link between them is when the compiler knows something. A value the compiler can see at compile time can be checked, folded into the instructions, and used where the language demands a constant. Text works the same way, which is why string literals and std::string_view behave so differently from std::string.

Constants

A constant is a value that cannot change while the program runs, and C++ has two kinds. A named constant is attached to an identifier, and a literal constant is written directly in the code with no name of its own.

A variable whose value cannot change is a constant variable, made with the const keyword. Every constant variable must be initialized, because there is no later opportunity to give it a value. const is worth skipping when passing by value or returning by value, since the caller and the callee are working with separate copies and the qualifier buys nothing.

const is a type qualifier, a keyword that modifies how a type behaves. As of C++23 the language has exactly two, const and volatile.

Compile Time Versus Run Time

A constant expression is one the compiler can evaluate during compilation. Anything else is a runtime expression.

That distinction sorts constants into two groups. A compile-time constant has a value known while compiling. A runtime constant is still constant, in that it never changes once set, but its value is not determined until the program runs.

constexpr requires the first kind: a constexpr variable must be initialized with a constant expression, and the compiler rejects anything else. Function parameters can never be constexpr, because their values arrive from the caller.

The practical payoff is that compile-time values can appear where the language requires a constant, such as an array length, and the compiler is free to compute them once during compilation instead of repeatedly at run time.

Literals and Magic Numbers

Literals are values written directly into the source. Every literal has a type, and a literal suffix overrides the default: 3.14 is a double, while 3.14f is a float.

A magic number is a literal whose meaning is not obvious from context, or one that may need to change later. Replacing it with a named symbolic constant fixes both problems at once, giving the value a name that explains it and a single place to edit it.

Numeral Systems

A numeral system is a set of digits used to write numbers. Everyday counting uses decimal, with ten digits. Computers work in binary, with two. C++ can also write octal and hexadecimal literals, which matter because hexadecimal maps neatly onto binary, with one hex digit covering exactly four bits.

System Base Digits
Decimal 10 0 to 9
Binary 2 0 and 1
Octal 8 0 to 7
Hexadecimal 16 0 to 9, then a to f

Strings

A string is a sequence of characters representing text. String literals sit between double quotes, and in C++ a string literal is a C-style string, whose type is awkward enough that you rarely want to handle it directly.

Two library types cover almost every case:

Owns its characters Cost Use for
std::string Yes Expensive to initialize, assign, and copy Text you need to store or modify
std::string_view No Cheap, copies nothing Read-only access to text that already exists

std::string lives in <string> and gives you safe, easy text handling. std::string_view provides read-only access to text that already exists somewhere else, whether that is a string literal, a std::string, or a character array. Because it copies nothing, it is the right choice for a function parameter that only reads.

Owning nothing is also its one hazard. A view whose underlying string has been destroyed is a dangling view. Modifying a std::string invalidates every view into it, since the modification may move the characters elsewhere in memory. Using an invalidated view, other than to point it somewhere valid again, is undefined behavior.

String literals are the exception that makes views convenient: they exist for the entire run of the program, so a std::string_view onto a literal can never dangle, and returning one from a function is safe.

A substring is a contiguous run of characters taken from within an existing string.

Terms Used in This Chapter

  • Constant: a value that cannot change while the program runs
  • Named constant and literal constant: a constant with an identifier, and one written directly in the code
  • Constant variable: a variable declared const, which must be initialized
  • Type qualifier: a keyword modifying a type's behavior, const or volatile
  • Constant expression and runtime expression: evaluable during compilation, or not
  • Compile-time constant and runtime constant: value known while compiling, or not until the program runs
  • constexpr: requires a compile-time constant, initialized from a constant expression
  • Literal: a value written directly in the source, with a type that suffixes can change
  • Magic number: an unexplained or change-prone literal, better replaced by a symbolic constant
  • Numeral system: the digits used to write numbers, such as decimal, binary, octal, and hexadecimal
  • String: a sequence of characters representing text
  • C-style string: the type a string literal actually has, awkward to work with directly
  • std::string: an owning, modifiable text type, expensive to copy
  • std::string_view: read-only, non-owning access to existing text, cheap to pass
  • Dangling view: a view whose underlying string has been destroyed
  • Invalidated view: a view into a std::string that has since been modified
  • Substring: a contiguous sequence of characters within a string

Looking Forward

The habit to take from this chapter is to say what you mean about change and ownership. Mark values const or constexpr when they should not move, name them instead of scattering literals, and pick the string type by whether the code owns the text or merely reads it. Both ideas return constantly, and the compile-time versus run-time distinction in particular grows more important as your programs get larger.