Coming Soon

This lesson is currently being developed

Stream states and input validation

Handle stream errors and validate input.

Input and Output (I/O)
Chapter
Beginner
Difficulty
45min
Estimated Time

What to Expect

Comprehensive explanations with practical examples

Interactive coding exercises to practice concepts

Knowledge quiz to test your understanding

Step-by-step guidance for beginners

Development Status

In Progress

Content is being carefully crafted to provide the best learning experience

Preview

Early Preview Content

This content is still being developed and may change before publication.

28.5 — Stream states and input validation

In this lesson, you'll learn about stream states and how to handle input validation effectively. Understanding stream states is crucial for writing robust programs that gracefully handle invalid input and I/O errors.

Understanding stream states

Every C++ stream maintains internal state flags that indicate whether operations are succeeding or failing. These states help you detect and respond to various error conditions.

#include <iostream>

int main()
{
    int number;
    
    std::cout << "Enter a number: ";
    std::cin >> number;
    
    std::cout << "Stream state after input:" << std::endl;
    std::cout << "good(): " << std::cin.good() << std::endl;
    std::cout << "fail(): " << std::cin.fail() << std::endl;
    std::cout << "bad(): " << std::cin.bad() << std::endl;
    std::cout << "eof(): " << std::cin.eof() << std::endl;
    
    return 0;
}

Example Run (valid input):

Enter a number: 42
Stream state after input:
good(): 1
fail(): 0
bad(): 0
eof(): 0

Example Run (invalid input):

Enter a number: hello
Stream state after input:
good(): 0
fail(): 1
bad(): 0
eof(): 0

The four stream state flags

1. goodbit (good())

  • Indicates no errors have occurred
  • All operations are working normally
  • Returns true when all other flags are false

2. failbit (fail())

  • Set when an operation fails (e.g., wrong data type)
  • Stream is still usable after clearing the error
  • Most common error state for input validation

3. badbit (bad())

  • Indicates a serious error (e.g., hardware failure)
  • Stream may be unusable
  • Less common than failbit

4. eofbit (eof())

  • Set when end-of-file is reached
  • Often used to detect when input is exhausted
#include <iostream>

void displayStreamState(std::istream& stream, const std::string& context)
{
    std::cout << context << ":" << std::endl;
    std::cout << "  good(): " << stream.good() << std::endl;
    std::cout << "  fail(): " << stream.fail() << std::endl;
    std::cout << "  bad(): " << stream.bad() << std::endl;
    std::cout << "  eof(): " << stream.eof() << std::endl;
    std::cout << std::endl;
}

int main()
{
    int number;
    
    displayStreamState(std::cin, "Initial state");
    
    std::cout << "Enter a number: ";
    std::cin >> number;
    
    displayStreamState(std::cin, "After input attempt");
    
    if (std::cin.fail())
    {
        std::cout << "Input failed!" << std::endl;
        std::cin.clear();  // Clear error flags
        displayStreamState(std::cin, "After clearing error");
    }
    else
    {
        std::cout << "Successfully read: " << number << std::endl;
    }
    
    return 0;
}

Checking stream states for validation

Basic validation pattern

#include <iostream>

int main()
{
    int number;
    
    std::cout << "Enter an integer: ";
    
    if (std::cin >> number)
    {
        std::cout << "Successfully read: " << number << std::endl;
    }
    else
    {
        std::cout << "Failed to read integer!" << std::endl;
        
        // Clear error state
        std::cin.clear();
        
        // Skip invalid input
        std::string invalidInput;
        std::cin >> invalidInput;
        std::cout << "Invalid input was: " << invalidInput << std::endl;
    }
    
    return 0;
}

Example Run:

Enter an integer: abc
Failed to read integer!
Invalid input was: abc

Robust input validation loop

#include <iostream>
#include <limits>

int getValidInteger(const std::string& prompt, int min = INT_MIN, int max = INT_MAX)
{
    int value;
    
    while (true)
    {
        std::cout << prompt;
        
        if (std::cin >> value)
        {
            // Check if value is in range
            if (value >= min && value <= max)
            {
                // Clear any remaining characters in the buffer
                std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
                return value;
            }
            else
            {
                std::cout << "Value must be between " << min << " and " << max << std::endl;
            }
        }
        else
        {
            std::cout << "Invalid input. Please enter an integer." << std::endl;
            
            // Clear error flags
            std::cin.clear();
            
            // Remove invalid input from buffer
            std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
        }
    }
}

int main()
{
    int age = getValidInteger("Enter your age (1-150): ", 1, 150);
    int score = getValidInteger("Enter test score (0-100): ", 0, 100);
    
    std::cout << "Age: " << age << std::endl;
    std::cout << "Score: " << score << std::endl;
    
    return 0;
}

Example Run:

Enter your age (1-150): abc
Invalid input. Please enter an integer.
Enter your age (1-150): -5
Value must be between 1 and 150
Enter your age (1-150): 25
Enter test score (0-100): 150
Value must be between 0 and 100
Enter test score (0-100): 95
Age: 25
Score: 95

Clearing stream states and buffers

The clear() method

#include <iostream>

int main()
{
    int number;
    
    std::cout << "Enter a number: ";
    std::cin >> number;
    
    if (std::cin.fail())
    {
        std::cout << "Input failed." << std::endl;
        std::cout << "Before clear() - fail(): " << std::cin.fail() << std::endl;
        
        std::cin.clear();  // Clear error flags
        std::cout << "After clear() - fail(): " << std::cin.fail() << std::endl;
        
        // Note: Invalid input is still in the buffer!
        std::cout << "Next character in buffer: ";
        char ch = std::cin.get();
        std::cout << "'" << ch << "'" << std::endl;
    }
    
    return 0;
}

The ignore() method

#include <iostream>
#include <limits>

int main()
{
    int number;
    
    std::cout << "Enter a number: ";
    std::cin >> number;
    
    if (std::cin.fail())
    {
        std::cout << "Input failed." << std::endl;
        
        // Clear error flags
        std::cin.clear();
        
        // Remove bad input from buffer
        std::cout << "Ignoring invalid input..." << std::endl;
        std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
        
        std::cout << "Buffer cleared. Try again:" << std::endl;
        std::cout << "Enter a number: ";
        
        if (std::cin >> number)
        {
            std::cout << "Successfully read: " << number << std::endl;
        }
    }
    
    return 0;
}

Different types of validation

Validating different data types

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

template<typename T>
T getValidInput(const std::string& prompt)
{
    T value;
    
    while (true)
    {
        std::cout << prompt;
        
        if (std::cin >> value)
        {
            // Clear buffer
            std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
            return value;
        }
        else
        {
            std::cout << "Invalid input. Please try again." << std::endl;
            std::cin.clear();
            std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
        }
    }
}

int main()
{
    int age = getValidInput<int>("Enter your age: ");
    double height = getValidInput<double>("Enter your height in feet: ");
    char grade = getValidInput<char>("Enter your letter grade: ");
    
    std::cout << "\nSummary:" << std::endl;
    std::cout << "Age: " << age << std::endl;
    std::cout << "Height: " << height << " feet" << std::endl;
    std::cout << "Grade: " << grade << std::endl;
    
    return 0;
}

Custom validation functions

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

bool isValidEmail(const std::string& email)
{
    // Simple validation: must contain @ and at least one dot after @
    size_t atPos = email.find('@');
    if (atPos == std::string::npos || atPos == 0 || atPos == email.length() - 1)
        return false;
    
    size_t dotPos = email.find('.', atPos);
    return dotPos != std::string::npos && dotPos < email.length() - 1;
}

std::string getValidEmail(const std::string& prompt)
{
    std::string email;
    
    while (true)
    {
        std::cout << prompt;
        std::getline(std::cin, email);
        
        if (isValidEmail(email))
        {
            return email;
        }
        else
        {
            std::cout << "Invalid email format. Please include @ and domain." << std::endl;
        }
    }
}

bool isValidPhoneNumber(const std::string& phone)
{
    // Simple validation: must be 10 digits, may contain hyphens or spaces
    int digitCount = 0;
    for (char c : phone)
    {
        if (isdigit(c))
        {
            digitCount++;
        }
        else if (c != '-' && c != ' ' && c != '(' && c != ')')
        {
            return false;  // Invalid character
        }
    }
    return digitCount == 10;
}

std::string getValidPhoneNumber(const std::string& prompt)
{
    std::string phone;
    
    while (true)
    {
        std::cout << prompt;
        std::getline(std::cin, phone);
        
        if (isValidPhoneNumber(phone))
        {
            return phone;
        }
        else
        {
            std::cout << "Invalid phone number. Must contain exactly 10 digits." << std::endl;
        }
    }
}

int main()
{
    std::cout << "User Registration" << std::endl;
    std::cout << "=================" << std::endl;
    
    std::string email = getValidEmail("Enter email address: ");
    std::string phone = getValidPhoneNumber("Enter phone number: ");
    
    std::cout << "\nRegistration successful!" << std::endl;
    std::cout << "Email: " << email << std::endl;
    std::cout << "Phone: " << phone << std::endl;
    
    return 0;
}

Menu-driven input validation

#include <iostream>
#include <limits>

int getMenuChoice(int minChoice, int maxChoice)
{
    int choice;
    
    while (true)
    {
        std::cout << "\nMenu Options:" << std::endl;
        for (int i = minChoice; i <= maxChoice; ++i)
        {
            std::cout << i << ". Option " << i << std::endl;
        }
        std::cout << "0. Exit" << std::endl;
        std::cout << "Enter choice (" << minChoice << "-" << maxChoice << ", 0 to exit): ";
        
        if (std::cin >> choice)
        {
            if (choice == 0 || (choice >= minChoice && choice <= maxChoice))
            {
                std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
                return choice;
            }
            else
            {
                std::cout << "Invalid choice. Please select " << minChoice 
                         << "-" << maxChoice << " or 0 to exit." << std::endl;
            }
        }
        else
        {
            std::cout << "Invalid input. Please enter a number." << std::endl;
            std::cin.clear();
            std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
        }
    }
}

int main()
{
    int choice;
    
    do
    {
        choice = getMenuChoice(1, 3);
        
        switch (choice)
        {
            case 1:
                std::cout << "You selected Option 1" << std::endl;
                break;
            case 2:
                std::cout << "You selected Option 2" << std::endl;
                break;
            case 3:
                std::cout << "You selected Option 3" << std::endl;
                break;
            case 0:
                std::cout << "Goodbye!" << std::endl;
                break;
        }
    }
    while (choice != 0);
    
    return 0;
}

Handling mixed input types

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

bool parseNameAndAge(const std::string& input, std::string& name, int& age)
{
    std::istringstream iss(input);
    
    // Try to read name (everything except the last word)
    std::string word;
    std::vector<std::string> words;
    
    while (iss >> word)
    {
        words.push_back(word);
    }
    
    if (words.size() < 2)
    {
        return false;  // Need at least name and age
    }
    
    // Try to parse the last word as age
    std::istringstream ageStream(words.back());
    if (!(ageStream >> age) || !ageStream.eof())
    {
        return false;  // Last word is not a valid integer
    }
    
    // Combine all but the last word as the name
    name = words[0];
    for (size_t i = 1; i < words.size() - 1; ++i)
    {
        name += " " + words[i];
    }
    
    return true;
}

int main()
{
    std::string input, name;
    int age;
    
    while (true)
    {
        std::cout << "Enter name and age (e.g., 'John Smith 25'): ";
        std::getline(std::cin, input);
        
        if (parseNameAndAge(input, name, age))
        {
            std::cout << "Name: " << name << std::endl;
            std::cout << "Age: " << age << std::endl;
            break;
        }
        else
        {
            std::cout << "Invalid format. Please enter name followed by age." << std::endl;
        }
    }
    
    return 0;
}

Stream state restoration

#include <iostream>
#include <limits>

class StreamStateGuard
{
private:
    std::istream& stream;
    std::ios_base::iostate originalState;
    
public:
    StreamStateGuard(std::istream& s) : stream(s), originalState(s.rdstate()) {}
    
    ~StreamStateGuard()
    {
        // Restore original state on destruction
        stream.clear(originalState);
    }
    
    void clearErrors()
    {
        stream.clear();
    }
};

int main()
{
    int number;
    
    {
        StreamStateGuard guard(std::cin);
        
        std::cout << "Enter a number: ";
        std::cin >> number;
        
        if (std::cin.fail())
        {
            std::cout << "Input failed, but state will be restored." << std::endl;
            guard.clearErrors();
            std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
        }
        else
        {
            std::cout << "Successfully read: " << number << std::endl;
        }
    }
    // Stream state is automatically restored here
    
    std::cout << "Stream state after guard destruction:" << std::endl;
    std::cout << "good(): " << std::cin.good() << std::endl;
    
    return 0;
}

Best practices for input validation

✅ Good practices:

#include <iostream>
#include <limits>

// Always provide clear error messages
int getPositiveInteger(const std::string& prompt)
{
    int value;
    
    while (true)
    {
        std::cout << prompt;
        
        if (std::cin >> value)
        {
            if (value > 0)
            {
                std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
                return value;
            }
            else
            {
                std::cout << "Error: Value must be positive (greater than 0)." << std::endl;
            }
        }
        else
        {
            std::cout << "Error: Please enter a valid integer." << std::endl;
            std::cin.clear();
            std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
        }
    }
}

// Validate ranges and provide helpful feedback
double getPercentage(const std::string& prompt)
{
    double value;
    
    while (true)
    {
        std::cout << prompt;
        
        if (std::cin >> value)
        {
            if (value >= 0.0 && value <= 100.0)
            {
                std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
                return value;
            }
            else
            {
                std::cout << "Error: Percentage must be between 0 and 100." << std::endl;
            }
        }
        else
        {
            std::cout << "Error: Please enter a valid number." << std::endl;
            std::cin.clear();
            std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
        }
    }
}

❌ Common mistakes:

#include <iostream>

int main()
{
    int number;
    
    // Bad: Not checking input success
    std::cout << "Enter a number: ";
    std::cin >> number;
    std::cout << "You entered: " << number << std::endl;  // May be garbage!
    
    // Bad: Not clearing error state
    if (std::cin.fail())
    {
        std::cout << "Error occurred" << std::endl;
        // Forgot std::cin.clear() - error state persists!
    }
    
    // Bad: Not clearing buffer
    std::cin.clear();  // Cleared error state but not the buffer
    std::cin >> number;  // May immediately fail again!
    
    return 0;
}

Summary

Effective stream state management and input validation involves:

  • Understanding stream states: good(), fail(), bad(), and eof()
  • Checking input success: Always verify that stream operations succeed
  • Clearing error states: Use clear() to reset error flags
  • Buffer management: Use ignore() to remove invalid input
  • Validation loops: Keep asking until valid input is received
  • Range checking: Validate that input values are within expected ranges
  • Custom validation: Create functions for complex validation rules
  • Error messages: Provide clear, helpful feedback to users

Proper input validation makes your programs more robust and user-friendly, preventing crashes and unexpected behavior from invalid input.

Quiz

  1. What are the four stream state flags and what do they indicate?
  2. What's the difference between clear() and ignore()?
  3. Why should you check the success of input operations?
  4. How do you validate that an integer is within a specific range?
  5. What happens if you don't clear the input buffer after a failed extraction?

Practice exercises

Test your input validation skills with these exercises:

  1. User Registration System: Create a program that validates username (alphanumeric only), password (minimum 8 characters), and email format.

  2. Calculator with Validation: Build a calculator that validates numeric input and operator input, handling all possible error conditions gracefully.

  3. Date Validator: Write a program that reads a date in MM/DD/YYYY format and validates that it's a real date (considering leap years).

  4. Survey System: Create a survey that asks multiple questions with different types of validation (age ranges, yes/no questions, ratings 1-5, etc.).

Continue Learning

Explore other available lessons while this one is being prepared.

View Course

Explore More Courses

Discover other available courses while this lesson is being prepared.

Browse Courses

Lesson Discussion

Share your thoughts and questions

💬

No comments yet. Be the first to share your thoughts!

Sign in to join the discussion