SovranCode
Modern C++ Lesson 21
Course contents50 available · 0 planned

Get a program running

01 · C++ Introduction: What Is C++?02 · How to Install a C++ Compiler and Set Up Your Editor03 · C++ Hello World: Your First Program Explained04 · How to Compile and Run C++ Programs05 · C++ Debugging: Warnings and Breakpoints

Work with values and input

06 · C++ Variables: Initialization and Scope07 · C++ Data Types: int, double and Type Conversion08 · C++ const, constexpr and auto Explained09 · C++ Operators: Arithmetic, Comparison and Logic10 · C++ User Input: cin, getline and Validation

Control flow and functions

11 · C++ Conditions: if, else if and else12 · C++ Switch Statement: Cases, break and default13 · C++ For Loop: Syntax and Examples14 · C++ While and Do-While Loops: Examples15 · C++ Functions: Parameters and Return Values

Strings and sequences

16 · C++ Strings: std::string with Examples17 · C++ Vector: std::vector with Examples18 · C++ Arrays: std::array and Bounds Checking19 · C++ Range-Based For Loop and Iterators20 · C++ Vector Practice: Build a Data Summary

Model data with classes

21 · C++ Structs and enum class Explained22 · C++ Classes and Objects: Encapsulation23 · C++ Constructors and Member Initialization24 · C++ Const Member Functions and Composition25 · C++ Copying Objects and the Rule of Zero

Lifetime and resource ownership

26 · C++ Object Lifetime, Destructors and RAII27 · C++ Pointers and References: nullptr and Borrowing28 · C++ unique_ptr: Smart Pointers and Ownership29 · C++ Move Semantics: std::move vs Copying30 · C++ string_view and span: Avoid Dangling Views

Containers and algorithms

31 · C++ map vs unordered_map: Lookup and Examples32 · C++ set, queue and stack: When to Use Each33 · C++ Lambda Expressions and STL Algorithms34 · C++ sort and binary_search: Rules and Complexity35 · C++20 Ranges: Views, filter and transform

Errors, files, and interfaces

36 · C++ optional and variant Explained37 · C++ Exceptions: try, catch and Safe Cleanup38 · C++ File Handling: Read and Write Text Files39 · C++ Input Parsing and Data Validation40 · C++ Polymorphism: Virtual Functions and Interfaces

Generic programming and quality

41 · C++ Templates: Function and Class Examples42 · C++20 Concepts and Template Constraints43 · C++ Unit Testing: Normal and Boundary Cases44 · C++ Sanitizers: Find Memory and Undefined Behavior Bugs45 · C++ Performance: Benchmark Before Optimizing

Build and deliver a complete application

46 · C++ Header Files and Translation Units47 · C++ CMake Tutorial: Build an Executable48 · C++ Dependencies and CTest: Automated Tests49 · C++ Multithreading: Threads and Data Races50 · C++ Command-Line Applications: Help and Release Checks
Modern C++50 available · 0 planned

Get a program running

01 · C++ Introduction: What Is C++?02 · How to Install a C++ Compiler and Set Up Your Editor03 · C++ Hello World: Your First Program Explained04 · How to Compile and Run C++ Programs05 · C++ Debugging: Warnings and Breakpoints

Work with values and input

06 · C++ Variables: Initialization and Scope07 · C++ Data Types: int, double and Type Conversion08 · C++ const, constexpr and auto Explained09 · C++ Operators: Arithmetic, Comparison and Logic10 · C++ User Input: cin, getline and Validation

Control flow and functions

11 · C++ Conditions: if, else if and else12 · C++ Switch Statement: Cases, break and default13 · C++ For Loop: Syntax and Examples14 · C++ While and Do-While Loops: Examples15 · C++ Functions: Parameters and Return Values

Strings and sequences

16 · C++ Strings: std::string with Examples17 · C++ Vector: std::vector with Examples18 · C++ Arrays: std::array and Bounds Checking19 · C++ Range-Based For Loop and Iterators20 · C++ Vector Practice: Build a Data Summary

Model data with classes

21 · C++ Structs and enum class Explained22 · C++ Classes and Objects: Encapsulation23 · C++ Constructors and Member Initialization24 · C++ Const Member Functions and Composition25 · C++ Copying Objects and the Rule of Zero

Lifetime and resource ownership

26 · C++ Object Lifetime, Destructors and RAII27 · C++ Pointers and References: nullptr and Borrowing28 · C++ unique_ptr: Smart Pointers and Ownership29 · C++ Move Semantics: std::move vs Copying30 · C++ string_view and span: Avoid Dangling Views

Containers and algorithms

31 · C++ map vs unordered_map: Lookup and Examples32 · C++ set, queue and stack: When to Use Each33 · C++ Lambda Expressions and STL Algorithms34 · C++ sort and binary_search: Rules and Complexity35 · C++20 Ranges: Views, filter and transform

Errors, files, and interfaces

36 · C++ optional and variant Explained37 · C++ Exceptions: try, catch and Safe Cleanup38 · C++ File Handling: Read and Write Text Files39 · C++ Input Parsing and Data Validation40 · C++ Polymorphism: Virtual Functions and Interfaces

Generic programming and quality

41 · C++ Templates: Function and Class Examples42 · C++20 Concepts and Template Constraints43 · C++ Unit Testing: Normal and Boundary Cases44 · C++ Sanitizers: Find Memory and Undefined Behavior Bugs45 · C++ Performance: Benchmark Before Optimizing

Build and deliver a complete application

46 · C++ Header Files and Translation Units47 · C++ CMake Tutorial: Build an Executable48 · C++ Dependencies and CTest: Automated Tests49 · C++ Multithreading: Threads and Data Races50 · C++ Command-Line Applications: Help and Release Checks
PREVIOUS LESSONC++ Vector Practice: Build a Data Summary
NEXT LESSONC++ Classes and Objects: Encapsulation
C++ foundations · Lesson 21 About 43 min with practice

C++ Structs and enum class Explained

Learn C++ structs and enum class with aggregate initialization, member access, value copying, explicit text parsing and a task-status modeling lab.

Before you begin

Complete functions, std::string, switch and vector. This lesson groups ordinary values; constructors and enforced class invariants follow next.

What you will learn

  • Group related values into a named struct instead of parallel arrays.
  • Initialize and access record members explicitly.
  • Use enum class for named domain states without implicit integer conversion.
  • Parse external text through an explicit accepted-state mapping.
  • Explain value copying and the limits of public mutable records.

A struct keeps the values for one thing together

A task title and task status describe the same record. Keeping titles in one vector and statuses in another makes their association depend on matching positions forever. Inserting into one collection but not the other can silently assign the wrong status to a title. A struct groups related members into one named type, so each element in a vector<Task> can carry its own coherent set of values together.

Declare struct Task with a braced member list and a semicolon after the closing brace. Members are public by default for a struct. Access a member on an object with the dot operator, such as task.title. The member's type still matters: a string manages text, while a dedicated status type names the permitted concepts. Grouping fields does not automatically explain their units, valid ranges or relationships, so document those contracts alongside the declaration.

For this lesson a Task is a small value record, not an object with a custom constructor or hidden allocation policy. Its std::string member handles its own owned text. We do not need new, delete, inheritance or a base class to represent a task. Start with the simplest model that expresses the data, then introduce encapsulation in the next lesson when preserving rules across every possible mutation becomes the central problem.

A Task value contains its own title string and a Status enum class value. External text passes through an explicit parser before becoming Todo, Doing or Done. A copied Task owns a separate title, while public fields still require callers to respect application rules.
A record groups related data; a scoped enum names its states. Input validation remains a separate boundary.

Replace magic status numbers with enum class

enum class Status { todo, doing, done }; declares a distinct type with scoped enumerator names. Write Status::done to identify the done state instead of relying on an unexplained number two or an arbitrary spelling scattered throughout the application. The scope allows different enumeration types to use similar member names without putting those enumerators directly into the surrounding scope. The type also makes function parameter intentions easier to read.

A scoped enum does not implicitly become an integer just because its representation is integral. You cannot assign an unrelated integer to Status and assume the compiler has validated a workflow state. The deliberately failing example demonstrates that boundary. An explicit cast can bypass some type friction, but it is not a parser and does not establish that an external number corresponds to a named business state. Prefer an explicit accepted mapping for user input.

Do not claim an enum class makes unnamed values impossible. Explicit conversions and lower-level interfaces can produce values outside your named states. A display function should therefore have a documented fallback or another explicit policy for unexpected input. Our statusName function covers all named cases and returns Unknown after the switch. That makes the function total for a supplied Status value without advertising the fallback as validation of malformed external data.

C++ PRACTICE RUNNER

Copy a task without sharing its owned title

The string and enum are value members. Updating the copied record does not change the original record.

#include <iostream>
#include <string>
enum class Status { todo, doing, done };
struct Task {
    std::string title;
    Status status{Status::todo};
};
int main() {
    const Task original{"Write tests", Status::todo};
    auto revised = original;
    revised.title = "Review tests";
    revised.status = Status::done;
    std::cout << original.title << '\n' << revised.title << '\n';
    std::cout << "Original still todo: " << (original.status == Status::todo) << '\n';
}
TERMINAL OUTPUT
Predict the output, edit the source, then select Run code.

C++20 compiler runner: source and input are sent only when you press Run to this website's execution endpoint, not an external vendor. Local preview requires Docker and gcc:14; production requires a separately isolated execution service. Network is disabled; execution is limited to 3 seconds, compilation to 10 seconds, memory to 384 MB and output to 32 KB. Single-file programs only. Don't enter secrets. Edits reset on refresh; Copy your work to keep it.

Compare with the starter's expected output
Write tests
Review tests
Original still todo: 1

After editing inputs, calculate a new prediction; this reference describes only the starter.

C++ PRACTICE RUNNER

Repair an integer-to-status assignment

Intentional compile failure. Replace the integer with the named Status::done enumerator rather than using a cast to hide an unvalidated mapping.

enum class Status { todo, doing, done };
int main() {
    Status state = 2;
    return state == Status::done ? 0 : 1;
}
TERMINAL OUTPUT
Predict the output, edit the source, then select Run code.

C++20 compiler runner: source and input are sent only when you press Run to this website's execution endpoint, not an external vendor. Local preview requires Docker and gcc:14; production requires a separately isolated execution service. Network is disabled; execution is limited to 3 seconds, compilation to 10 seconds, memory to 384 MB and output to 32 KB. Single-file programs only. Don't enter secrets. Edits reset on refresh; Copy your work to keep it.

Initialize records deliberately and understand defaults

The simple Task declaration here is an aggregate, so braces can supply its members in declaration order. Task{"Write tests", Status::todo} initializes title and status together. A default member initializer such as Status status{Status::todo} makes an omitted status deliberate in this record shape. Do not mentally reorder positional initializer values based on their apparent meaning; the declared member order is the contract for ordinary aggregate list initialization.

For numeric members without defaults, a local declaration with no initializer can leave values uninitialized. Using braces or explicit member defaults avoids that mistake. std::string has its own initialization behavior, but that does not mean every other member magically gains a sensible value. Review each member's initialization individually. A record with an initialized string and an indeterminate integer still has an unsafe integer read if you use it before assignment.

C++20 has designated initialization for suitable aggregates, but it has restrictions and is not a reason to ignore member-order rules. This lesson uses the simpler positional form and explicit defaults. As records grow, names, validation and construction interfaces become more important than compressing object creation onto one line. The next class lesson will show how a constructor can establish rules instead of asking every caller to remember the same validation sequence.

Convert external text through a deliberate parser

The lab reads a title line and a status line. Accepted status spellings are exactly todo, doing and done; the comparison is case-sensitive and does not trim or normalize text. The parser assigns an output Status only when a spelling matches. It returns false otherwise, leaving the caller responsible for rejecting the record. This connects the parsing result to the permitted names without treating arbitrary text as a trusted domain value.

Parsing and display are different functions. parseStatus handles external spellings and reports whether conversion succeeded; statusName maps a known stored state to a presentation label. A display function returning Unknown is not permission to accept every status input. The caller must check parsing success before constructing and reporting the accepted Task. Keeping both directions explicit also makes later changes, such as translated labels, less likely to accidentally redefine the input protocol.

A struct alone does not enforce a nonempty title. Its public string can be changed after successful input validation, and an empty aggregate could still be constructed elsewhere. Our lab validates a title of one through forty bytes with at least one byte other than an ordinary space. This is a narrow text policy, not Unicode normalization, grapheme counting or a universal definition of blank text. In particular, byte length is not a count of user-perceived characters.

Value modeling is useful even before full encapsulation

A copy of this Task owns an independent std::string and copies the enum value. Changing the copy's title does not affect the original title. That statement relies on the current member types; it would need reconsideration if the record instead stored pointers to shared mutable objects. Passing const Task& to a display function avoids unnecessary copies while preventing mutation through that reference. It does not make the original record globally immutable.

The report prints Title and Status, using Todo, Doing or Done for accepted states. Missing required lines produce Invalid input, a rejected title produces Invalid title, and an unsupported status spelling produces Invalid status; each failure returns status one without a success report. Extra input after the two required lines is not another task and remains unread. The explicit branches make failure transcripts testable instead of silently repairing the user's input.

Test every accepted status, an unknown word, a capitalization mismatch, an empty title, an ordinary-space-only title, the forty-byte boundary and a forty-one-byte title. Also test end-of-input after the title and a valid status on a final line without a newline. Then explain which rules the public struct itself cannot enforce. That reflection prepares you for encapsulation: grouping data improves coherence, while protecting invariants requires an interface that controls construction and mutation.

LOCAL TERMINAL COMMANDS

Compile and test outside the lesson

The compiler-backed editors support complete single-file programs when the execution service is available. Otherwise save one program as main.cpp and run these commands with your configured compiler. Supply all standard input before Run; this is not an interactive terminal.

macOS / Linux (choose your installed compiler):
clang++ -std=c++20 -Wall -Wextra -Wpedantic main.cpp -o lesson
# Or: g++ -std=c++20 -Wall -Wextra -Wpedantic main.cpp -o lesson
./lesson

Windows Developer Command Prompt:
cl /std:c++20 /EHsc /W4 main.cpp /Fe:lesson.exe
lesson.exe
APPLY THE CONCEPT

Independent lab

Parse a two-line task record into a struct with a scoped status, then print only a validated task.

Acceptance checklist

  • Define Status with todo, doing and done, and Task with a title and status.
  • Read title and status as two complete lines; missing required input prints Invalid input and status 1.
  • Accept a 1–40-byte title containing something other than ordinary spaces; otherwise print Invalid title and status 1.
  • Accept exactly todo, doing or done through an explicit parser; otherwise print Invalid status and status 1.
  • Print Title and the corresponding Todo/Doing/Done label only after validation; do not cast arbitrary integers or strings into a state.
Need a hint?
  1. A bool parser with a Status& output can distinguish accepted mapping from failure.
  2. Validate title.empty, title.size and find_first_not_of(' ') separately from status parsing.
  3. A switch over Status makes the output labels visible; keep an explicit unexpected-value fallback.
C++ PRACTICE RUNNER

Write and run your lab

Build your own solution before revealing the reference. Use the input field for normal, boundary and invalid cases; this runner executes your program but does not grade it.

#include <iostream>
#include <string>
#include <sstream>
#include <limits>

int main() {
    // Implement the acceptance checklist above.
    return 0;
}

TERMINAL OUTPUT
Predict the output, edit the source, then select Run code.

C++20 compiler runner: source and input are sent only when you press Run to this website's execution endpoint, not an external vendor. Local preview requires Docker and gcc:14; production requires a separately isolated execution service. Network is disabled; execution is limited to 3 seconds, compilation to 10 seconds, memory to 384 MB and output to 32 KB. Single-file programs only. Don't enter secrets. Edits reset on refresh; Copy your work to keep it.

Your lab evidence and notes

Record predictions, actual output, commands and the cause of each repair. Code is plain text: this scratchpad does not execute or grade C++.

Loading local notes…

Review your acceptance checklist and quiz explanations before marking this lesson complete. This is self-review, not a grade or certification.

Reveal the explained lab solution
C++ PRACTICE RUNNER

Reference lab solution

Compare the reference with your own reasoning. Running this solution does not grade or complete the lesson.

#include <iostream>
#include <string>
enum class Status { todo, doing, done };
struct Task {
    std::string title;
    Status status{Status::todo};
};
bool parseStatus(const std::string& text, Status& status) {
    if (text == "todo") status = Status::todo;
    else if (text == "doing") status = Status::doing;
    else if (text == "done") status = Status::done;
    else return false;
    return true;
}
std::string statusName(Status status) {
    switch (status) {
        case Status::todo: return "Todo";
        case Status::doing: return "Doing";
        case Status::done: return "Done";
    }
    return "Unknown";
}
int main() {
    std::string title, text;
    if (!std::getline(std::cin, title) || !std::getline(std::cin, text)) {
        std::cout << "Invalid input\n"; return 1;
    }
    if (title.empty() || title.size() > 40 || title.find_first_not_of(' ') == std::string::npos) {
        std::cout << "Invalid title\n"; return 1;
    }
    Status status{Status::todo};
    if (!parseStatus(text, status)) { std::cout << "Invalid status\n"; return 1; }
    const Task task{title, status};
    std::cout << "Title: " << task.title << "\nStatus: " << statusName(task.status) << '\n';
}
TERMINAL OUTPUT
Predict the output, edit the source, then select Run code.

C++20 compiler runner: source and input are sent only when you press Run to this website's execution endpoint, not an external vendor. Local preview requires Docker and gcc:14; production requires a separately isolated execution service. Network is disabled; execution is limited to 3 seconds, compilation to 10 seconds, memory to 384 MB and output to 32 KB. Single-file programs only. Don't enter secrets. Edits reset on refresh; Copy your work to keep it.

Expected output

Title: Write tests
Status: Doing

Text parsing happens before constructing the accepted record. A rejected status never becomes a success report through a fallback label.

The title limit is a documented byte policy with ordinary-space detection, not a promise about Unicode characters or all whitespace.

The struct groups values and the enum expresses named states, but public fields still do not enforce title invariants across arbitrary future mutations.

Review before moving on

Explain the lesson's objectives in your own words, compare your lab evidence with the acceptance checklist, and keep the first failing case alongside your repair. Completing a page is not proof that every C++ feature or input is supported.

  • Group related values into a named struct instead of parallel arrays.
  • Initialize and access record members explicitly.
  • Use enum class for named domain states without implicit integer conversion.
  • Parse external text through an explicit accepted-state mapping.
  • Explain value copying and the limits of public mutable records.

References and further reading

Original examples and exercises accompany these primary references. Tool-specific diagnostics and support depend on your installed version.

  • Microsoft: scoped enumerations
  • C++ draft: enumeration declarations
  • C++ draft: aggregate initialization
KNOWLEDGE CHECK

Check your understanding

Answer all five questions, then review the explanations. Answers are not saved or synced; completion is a separate self-review choice.

01What does a struct solve compared with two parallel vectors?
02How do you name the done enumerator of a scoped Status enum?
03Does enum class guarantee every possible value names a listed state?
04Does copying this Task share its title string object?
05What remains unenforced by a public Task record?
PREVIOUS LESSONC++ Vector Practice: Build a Data Summary
NEXT LESSONC++ Classes and Objects: Encapsulation
ON THIS PAGELearning objectivesA struct keeps the values for one thing togetherReplace magic status numbers with enum classInitialize records deliberately and understand defaultsConvert external text through a deliberate parserValue modeling is useful even before full encapsulationIndependent labReviewKnowledge checkReferences
Course overview