SovranCode
Modern C++ Lesson 39
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++ File Handling: Read and Write Text Files
NEXT LESSONC++ Polymorphism: Virtual Functions and Interfaces
C++ foundations · Lesson 39 About 48 min with practice

C++ Input Parsing and Data Validation

Learn C++ input parsing and validation in clear steps: whole lines, exact record syntax, allowed values, useful line numbers and no partial success output.

Before you begin

Know strings, loops, vectors and optional. This lesson uses a deliberately small text format, not a general CSV or Unicode parser.

What you will learn

  • Separate reading text, parsing syntax and checking meaning.
  • Write down a small format before coding its parser.
  • Reject extra separators, suffixes and unsupported characters.
  • Report the first invalid record with a useful position.
  • Collect valid records before printing a success report.

Text is not valid data just because it was read

getline can read the text apple:12, apple:2cats or apple:99. Reading a line successfully tells you that text was obtained. It does not tell you that the text follows your format or that its number is suitable. Parsing means turning text that follows a stated structure into values. Validation means checking whether those values satisfy the task's rules. Keep these steps separate so failures are easy to explain.

We use a small record format: a name, one colon, and a quantity. The name has one through twelve lowercase English letters. The quantity has one or two decimal digits and its value must be zero through twenty. There are no spaces, signs, extra fields or empty parts. Leading zeroes within the two-digit limit are accepted, so apple:02 means two. These are written choices, not universal rules for text files.

A narrow format is a good learning tool because every accepted and rejected example can be explained. It is not general CSV, JSON, a spreadsheet import or a language parser. Those formats have their own rules for quoting, escaping, encodings and numbers. Do not call this parser CSV merely because records are on lines. The promise you make in documentation should match the text your program actually understands.

The record apple colon twelve passes a syntax check for name, separator and digits, then a value check from zero to twenty. apple colon 2x fails syntax, while apple colon 99 has numeric syntax but fails the allowed value rule.
Reading, syntax and meaning are different checks. A numeric prefix is not enough: the whole quantity field must match the documented format before its bounded value is accepted.
C++ PRACTICE RUNNER

Reject a numeric suffix instead of accepting a prefix

Formatted extraction alone can stop after twelve. The second check requires the rest of the field to be whitespace only.

#include <iostream>
#include <sstream>
#include <string>
bool wholeInteger(const std::string& text, int& value) {
    std::istringstream parser{text};
    if (!(parser >> value)) return false;
    parser >> std::ws;
    return parser.eof();
}
int main() {
    int value{};
    if (wholeInteger("12cats", value)) std::cout << "Accepted\n";
    else std::cout << "Rejected\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
Rejected

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

Write the accepted shape before the implementation

Start with ordinary examples. apple:0 and pear:20 are valid. :2 has no name. apple: has no quantity. apple:2:3 has an extra colon. Apple:2 has an uppercase letter, which our rule excludes. apple:21 has the right general shape but a value outside the allowed range. A short list like this is more useful than beginning with conversion code and discovering its accidental behavior later.

Find the first colon and require it to be the only colon. Then check the lengths of the two parts. Loop over the name characters and require each one to belong to the explicit lowercase alphabet. Loop over the quantity characters and require each one to be a decimal digit. We deliberately use explicit character sets rather than claiming to recognize all letters from all languages. No whitespace is trimmed from record lines.

Only after the digit and length checks do we build the quantity. Starting from zero, multiply by ten and add the next digit. With at most two digits, the intermediate number cannot exceed ninety-nine, so this calculation cannot overflow int on a conforming implementation. Finally check zero through twenty. If you later allow longer fields, you must revisit overflow protection rather than copying this bounded calculation into an unlimited parser.

  • apple:12 splits into apple and 12.
  • The name is nonempty lowercase text and has allowed length.
  • Both quantity characters are digits; conversion gives 12.
  • 12 is in 0..20, so the record is accepted.
C++ PRACTICE RUNNER

Parse one small record with explicit checks

optional<Record> returns a complete accepted record or no record. It never exposes a half-filled successful result.

#include <iostream>
#include <optional>
#include <string>
struct Record { std::string name; int quantity; };
std::optional<Record> parseRecord(const std::string& line) {
    const auto colon = line.find(':');
    if (colon == std::string::npos || line.find(':', colon + 1) != std::string::npos)
        return std::nullopt;
    const auto name = line.substr(0, colon);
    const auto digits = line.substr(colon + 1);
    if (name.empty() || name.size() > 12 || digits.empty() || digits.size() > 2)
        return std::nullopt;
    const std::string letters{"abcdefghijklmnopqrstuvwxyz"};
    const std::string decimal{"0123456789"};
    for (char ch : name) if (letters.find(ch) == std::string::npos) return std::nullopt;
    int quantity{};
    for (char ch : digits) {
        const auto digit = decimal.find(ch);
        if (digit == std::string::npos) return std::nullopt;
        quantity = quantity * 10 + static_cast<int>(digit);
    }
    if (quantity > 20) return std::nullopt;
    return Record{name, quantity};
}
int main() {
    const auto record = parseRecord("apple:12");
    if (record) std::cout << record->name << ": " << record->quantity << '\n';
    else std::cout << "Rejected\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
apple: 12

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

Each early return answers one small question

The parser does not need one giant condition that does everything at once. Each early return rules out one kind of failure before later work depends on it. The separator checks happen before slicing the two fields. Length checks happen before bounded numeric conversion. Digit checks happen before using a character as a number. This order makes the function readable and explains why an invalid input cannot sneak through just because its beginning looked correct.

The local name and quantity are work in progress. Only the final return creates a successful Record value. optional makes the success-or-absence boundary visible, and the caller checks presence before using the record. For an application that needs detailed reasons, you can return a richer error type. A single missing result is enough for this lesson because the surrounding loop adds the record number and stops at the first failure.

Duplicate names are allowed here. The parser checks the shape and quantity of one record, not uniqueness across the whole collection. A business rule such as no repeated item name belongs in a later collection-level validation step. Do not quietly add rules that were never documented. A parser that rejects a valid repeated name can be just as wrong for its contract as one that accepts a malformed quantity.

C++ PRACTICE RUNNER

Keep the same rule for edge cases

This small independent check shows the difference between a zero, a leading zero, an out-of-range value and invalid text.

#include <iostream>
#include <optional>
#include <string>
std::optional<int> quantityOf(const std::string& text) {
    if (text.empty() || text.size() > 2) return std::nullopt;
    const std::string digits{"0123456789"};
    int value{};
    for (char ch : text) {
        const auto digit = digits.find(ch);
        if (digit == std::string::npos) return std::nullopt;
        value = value * 10 + static_cast<int>(digit);
    }
    if (value > 20) return std::nullopt;
    return value;
}
int main() {
    for (const std::string text : {"0", "02", "20", "21", "2x"}) {
        const auto value = quantityOf(text);
        std::cout << text << ": ";
        if (value) std::cout << *value << '\n';
        else std::cout << "Rejected\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
0: 0
02: 2
20: 20
21: Rejected
2x: Rejected

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

Report the first bad record without a false success report

When reading several records, keep a one-based record number for the user. If the third required line is missing or invalid, report Invalid record: 3. That points the learner to the failing data without printing a partial total that looks complete. It is a record number after the count header, not the physical file line number including that header. Choose and document one meaning so the report cannot be misread.

Collect accepted records in a temporary vector. Print the success report only after all required records pass. If you later load this into an existing application state, use the candidate-and-commit plan from the exceptions lesson. Validation and state updates should not be interleaved accidentally. A program that prints three accepted records and then fails on the fourth needs to explain that the report is partial; our lab avoids that ambiguity entirely.

Do not fix malformed input by deleting characters until it becomes valid unless that normalization is an explicit requirement. apple:2x must not quietly become apple:2. Leading and trailing spaces on record lines are rejected under our exact rules, even though the separate count reader accepts surrounding whitespace. Different fields can have different contracts. The important point is that these choices are visible, consistent and covered by examples.

Build a tiny record import with honest failure

Read a whole-line count from zero through five, then exactly that many required record lines in the documented name:quantity format. A malformed or out-of-range count prints Invalid input with status one. A missing or invalid required record prints Invalid record followed by its one-based record number, with status two. Stop at that first error. Lines beyond the required count are outside this small exercise's input contract.

On success, print Records followed by the count and Total followed by the sum. Keep duplicate names and zero quantities. At most five quantities of twenty keep the total within one hundred. Test empty data, maximum lengths, leading zeroes, extra colons, spaces, missing lines and an invalid last record after several valid ones. Be able to explain whether each rejected example fails structure, character rules or allowed value. That explanation matters more than a long list of conversions.

LOCAL TERMINAL COMMANDS

Compile and check one complete program

Save one example as main.cpp. Choose your installed compiler. Run only after a successful build. The in-page editor needs the configured compiler sandbox; these terminal commands are an alternative, not proof that the sandbox is running.

macOS / Linux:
clang++ -std=c++20 -Wall -Wextra -Wpedantic main.cpp -o lesson
# Or use g++ with the same options.
./lesson

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

Independent lab

Import a small exact text format without accepting prefixes or reporting partial success.

Acceptance checklist

  • Read count 0..5 on a complete integer line; reject a bad count with Invalid input and status 1.
  • Require one colon, a 1..12 lowercase-English-letter name, and 1..2 quantity digits with value 0..20; accept two-digit leading zeroes.
  • Do not trim record lines or accept signs, extra fields or numeric suffixes; allow repeated names.
  • Return optional<Record>; stop on missing or invalid required record with Invalid record: N and status 2.
  • After all records pass, print Records and Total; support zero records and keep the total bounded.
Need a hint?
  1. Check the colon and lengths before slicing or converting fields.
  2. The two-digit rule makes manual conversion bounded by 99 before the domain check.
  3. Store accepted records first and report only after the full required group passes.
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 <sstream>
#include <string>
bool readInteger(int& value) {
    std::string line;
    if (!std::getline(std::cin, line)) return false;
    std::istringstream parser{line};
    if (!(parser >> value)) return false;
    parser >> std::ws;
    return parser.eof();
}
#include <optional>
#include <vector>
struct Record { std::string name; int quantity; };
std::optional<Record> parseRecord(const std::string& line) {
    const auto colon = line.find(':');
    if (colon == std::string::npos || line.find(':', colon + 1) != std::string::npos)
        return std::nullopt;
    const auto name = line.substr(0, colon);
    const auto digits = line.substr(colon + 1);
    if (name.empty() || name.size() > 12 || digits.empty() || digits.size() > 2)
        return std::nullopt;
    const std::string letters{"abcdefghijklmnopqrstuvwxyz"};
    const std::string decimal{"0123456789"};
    for (char ch : name) if (letters.find(ch) == std::string::npos) return std::nullopt;
    int quantity{};
    for (char ch : digits) {
        const auto digit = decimal.find(ch);
        if (digit == std::string::npos) return std::nullopt;
        quantity = quantity * 10 + static_cast<int>(digit);
    }
    if (quantity > 20) return std::nullopt;
    return Record{name, quantity};
}
int main() {
    int count{};
    if (!readInteger(count) || count < 0 || count > 5) {
        std::cout << "Invalid input\n"; return 1;
    }
    std::vector<Record> records;
    for (int i = 0; i < count; ++i) {
        std::string line;
        const bool read = static_cast<bool>(std::getline(std::cin, line));
        const auto record = read ? parseRecord(line) : std::nullopt;
        if (!record) { std::cout << "Invalid record: " << i + 1 << '\n'; return 2; }
        records.push_back(*record);
    }
    int total{};
    for (const auto& record : records) total += record.quantity;
    std::cout << "Records: " << records.size() << "\nTotal: " << total << '\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

Records: 3
Total: 22

Whole-line reading does not itself validate a record. The parser checks every separator, character, length and bounded quantity.

The error number counts required records after the header. No success count or total is printed when a later record fails.

Leading zeroes within two digits and duplicate names are accepted because the contract says so. No undocumented normalization changes rejected input.

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.

  • Separate reading text, parsing syntax and checking meaning.
  • Write down a small format before coding its parser.
  • Reject extra separators, suffixes and unsupported characters.
  • Report the first invalid record with a useful position.
  • Collect valid records before printing a success report.

References and further reading

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

  • C++ draft: string operations
  • C++ draft: string input and output
  • C++ draft: optional
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.

01getline read apple:99. Is the record automatically valid?
02Should apple:2cats become apple:2 here?
03Is apple:02 allowed by this lesson's format?
04An invalid third record follows two valid ones. What is printed?
05Does this parser enforce unique names?
PREVIOUS LESSONC++ File Handling: Read and Write Text Files
NEXT LESSONC++ Polymorphism: Virtual Functions and Interfaces
ON THIS PAGELearning objectivesText is not valid data just because it was readWrite the accepted shape before the implementationEach early return answers one small questionReport the first bad record without a false success reportBuild a tiny record import with honest failureIndependent labReviewKnowledge checkReferences
Course overview