SovranCode
Modern C++ Lesson 42
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++ Templates: Function and Class Examples
NEXT LESSONC++ Unit Testing: Normal and Boundary Cases
C++ foundations · Lesson 42 About 47 min with practice

C++20 Concepts and Template Constraints

Learn C++20 concepts and requires in simple steps: name a type rule, read a failed constraint, and keep compile-time checks separate from runtime value validation.

Before you begin

Know function templates and their type requirements. The examples use C++20 concepts; selecting a language flag still requires a toolchain with the needed library support.

What you will learn

  • Explain a concept as a named rule for types.
  • Use a constrained template with compatible arguments.
  • Read requires as a condition on a template use.
  • Separate type suitability from allowed input values.
  • Test accepted and rejected types as well as runtime bounds.

Name the type rule instead of hiding it in a long error

The previous lesson used a template with a requirement: its values had to support a useful comparison. If the wrong type reached the function body, the compiler could produce a long message about the failed operation. A concept lets you name a type rule and use that rule at the template's boundary. It helps say which uses are supported before a reader has to inspect every line of the body.

A concept is a compile-time true-or-false condition on template arguments. Compile time means while the program is being built, not when a user enters text into the running program. A constraint applies such a condition to a template use. C++20 provides standard concepts in the concepts header. std::integral describes integral types, including int, long, character types and bool. It does not mean a particular number is positive or small enough for your task.

Our CountInteger rule accepts integral types but excludes bool. A boolean is useful for yes-or-no answers, not for the numeric count interface we are teaching. This rule is still not a complete business policy. It does not limit a count to twenty or check the ordering of two bounds. Those are questions about values and must be checked at runtime when the values come from input.

A compile-time type gate accepts an integer type and rejects double and bool for CountInteger. A separate runtime gate accepts integer twelve within zero through twenty but rejects integer ninety-nine even though its type was accepted.
A valid type is not automatically a valid value. Concepts describe template-argument requirements; input bounds, object lifetime and real task meaning still need their own checks.
C++ PRACTICE RUNNER

Use a small named type rule

Both calls use one consistent type per call. boolalpha prints readable true-or-false results.

#include <iostream>
#include <concepts>
#include <type_traits>
template<typename T>
concept CountInteger = std::integral<T> && (!std::same_as<std::remove_cv_t<T>, bool>);
template<CountInteger T>
bool within(T value, T lower, T upper) {
    return lower <= value && value <= upper;
}
int main() {
    std::cout << std::boolalpha;
    std::cout << within(12, 0, 20) << '\n';
    std::cout << within(99L, 0L, 20L) << '\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
true
false

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

Read the definition, then the call

CountInteger combines two questions with &&. First, does std::integral<T> hold? Second, is the underlying type different from bool? remove_cv_t removes const and volatile for that type comparison, so const bool cannot slip past the exclusion. same_as asks whether two types are the same. The function declaration template<CountInteger T> applies the named requirement. The body still looks like ordinary function code.

For within(12, 0, 20), deduction chooses int for all three arguments. The type rule passes. The function then compares the actual values and returns true. For the long call, the L suffix makes all three arguments long. That type passes too, but ninety-nine is outside the bounds, so the running function returns false. Passing the concept and passing the value check are two separate events.

The function assumes its caller has a meaningful lower and upper bound. If you pass lower greater than upper, no integer can pass both comparisons, but that may hide a malformed request. The lab rejects reversed bounds explicitly before calling within. A concept cannot inspect arbitrary values read later from cin. Avoid calling a type restriction validation when the actual user-input policy has not been implemented.

  • Deduction chooses int for 12, 0 and 20.
  • CountInteger<int> is true, so this template use is permitted.
  • The running function checks 0 <= 12 and 12 <= 20.
  • The result is true; a different integer value may still be rejected.
C++ PRACTICE RUNNER

Repair a rejected floating-point call

Intentional compile error. The rule excludes double. Use an integral count when that is the real task, rather than weakening the rule just to silence the diagnostic.

#include <concepts>
#include <type_traits>
template<typename T>
concept CountInteger = std::integral<T> && (!std::same_as<std::remove_cv_t<T>, bool>);
template<CountInteger T>
bool within(T value, T lower, T upper) {
    return lower <= value && value <= upper;
}
int main() {
    return within(2.5, 0.0, 20.0) ? 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.

requires states a requirement, not a runtime branch

Another spelling is template<typename T> requires CountInteger<T>, followed by the function declaration. The next example uses that form. It expresses the same type restriction as the shorter constrained-parameter spelling. Start with one style in your own small project so readers can recognize the pattern. You do not need several layers of nested requirements to describe this straightforward type choice.

A requires-expression can ask whether particular expressions are valid and can constrain properties of their types. That is useful for richer templates, but this lesson deliberately starts with standard concepts and one named combination. An expression compiling is not proof that it has the right business meaning, is cheap, or can never throw. A comparison might be valid yet sort records by the wrong field for your report.

Also, a concept does not automatically make every line of a function safe. If the body performs arithmetic that can overflow, the integral type rule does not prevent that. Our within helper only compares bounded values; it does not add them. Keep runtime preconditions and arithmetic limits visible. Constraints improve the contract and diagnostics, but they are not a replacement for tests, lifetime reasoning or a clear algorithm.

C++ PRACTICE RUNNER

Read the same rule with requires

The boolean type test is a compile-time check. No invalid boolean call is executed.

#include <iostream>
#include <concepts>
#include <type_traits>
template<typename T>
concept CountInteger = std::integral<T> && (!std::same_as<std::remove_cv_t<T>, bool>);
template<CountInteger T>
bool within(T value, T lower, T upper) {
    return lower <= value && value <= upper;
}
template<typename T> requires CountInteger<T>
T chooseLarger(T first, T second) {
    return first < second ? second : first;
}
static_assert(!CountInteger<bool>);
static_assert(!CountInteger<const bool>);
static_assert(CountInteger<int>);
int main() { std::cout << chooseLarger(3, 7) << '\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
7

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

Fix the reason for rejection, not just the message

When a constrained call fails, begin with the deduced types shown in the diagnostic. Are they the types you intended? A literal with a decimal point suggests double; an ordinary small whole-number literal suggests int. Mixed int and long arguments can still conflict during deduction because the function asks for one T. A concept does not automatically invent a conversion plan for unrelated argument types.

Do not cast every input to int to force acceptance. A conversion can lose information and may not preserve the task's meaning. Instead decide whether the interface truly describes integral counts. If a new task needs measurements with fractions, it may deserve another function and a different rule. Reusable code should make intended uses easier to understand, not make every possible use compile at the cost of a misleading contract.

Test both sides of the boundary. A small compile-time assertion can confirm that int is admitted and bool is excluded. An intentional rejected double call can demonstrate the compiler diagnostic separately. Runtime tests must still cover the lower value, upper value, values outside the interval and reversed input bounds. These tests answer different questions. A successful build proves type checks passed for those uses, not that every future input will be valid.

Compare bounded input using int and long

Read value, lower and upper on three complete integer lines. All must be between minus one hundred and one hundred, and lower must not exceed upper. Bad syntax, missing input, out-of-range values or reversed bounds print Invalid input with status one. Otherwise call within with int values and again with explicitly converted long values. These small accepted integers convert exactly to long.

Print Inside and Long inside using boolalpha. Both answers should agree, including exact endpoints and a one-value interval. Add compile-time assertions for accepted int and rejected bool. Keep the rejected double example separate from the valid lab program. Explain which rules were checked during compilation and which depended on the actual input. That explanation is more useful than memorizing requires without knowing when its condition is evaluated.

LOCAL TERMINAL COMMANDS

Build one complete safe example

Choose an installed compiler. These commands run locally, not in the source editor. The in-page Run button requires the configured compiler sandbox. Diagnostic fixtures and multi-file projects use their separate instructions.

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

Use a named C++20 type constraint without confusing it with runtime validation.

Acceptance checklist

  • Define CountInteger from integral and exclusion of bool; constrain within with it.
  • Read value, lower and upper as whole integers in -100..100 and reject reversed bounds.
  • Reject invalid required input as Invalid input with status 1 before reporting.
  • Call within using int, then long arguments converted from the accepted small values.
  • Print Inside and Long inside with boolalpha, and include static assertions for accepted int and rejected bool.
Need a hint?
  1. All three parameters of one within call use the same T.
  2. Check the input domain and lower <= upper before the calls.
  3. A type assertion does not replace tests of actual endpoint values.
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 <concepts>
#include <type_traits>
template<typename T>
concept CountInteger = std::integral<T> && (!std::same_as<std::remove_cv_t<T>, bool>);
template<CountInteger T>
bool within(T value, T lower, T upper) {
    return lower <= value && value <= upper;
}
static_assert(CountInteger<int>);
static_assert(!CountInteger<bool>);
int main() {
    int value{}, lower{}, upper{};
    if (!readInteger(value) || !readInteger(lower) || !readInteger(upper)
        || value < -100 || value > 100 || lower < -100 || lower > 100
        || upper < -100 || upper > 100 || lower > upper) {
        std::cout << "Invalid input\n"; return 1;
    }
    std::cout << std::boolalpha << "Inside: " << within(value, lower, upper)
        << "\nLong inside: " << within(static_cast<long>(value), static_cast<long>(lower), static_cast<long>(upper)) << '\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

Inside: true
Long inside: true

The type constraint admits int and long while rejecting bool and double for this interface.

The input checks enforce bounds and a meaningful interval. Those facts are not implied by integral<T>.

Both calls compare the same small exact values. Their agreement is checked at normal, boundary and outside-interval cases, not assumed from a successful compilation.

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.

  • Explain a concept as a named rule for types.
  • Use a constrained template with compatible arguments.
  • Read requires as a condition on a template use.
  • Separate type suitability from allowed input values.
  • Test accepted and rejected types as well as runtime bounds.

References and further reading

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

  • C++ draft: constraints
  • C++ draft: standard concepts
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.

01When does a concept constrain a template use?
02Does integral<int> mean an integer is in 0..20?
03Why exclude bool from CountInteger?
04Can requires prove a comparator has the right business meaning?
05What should happen to reversed input bounds in the lab?
PREVIOUS LESSONC++ Templates: Function and Class Examples
NEXT LESSONC++ Unit Testing: Normal and Boundary Cases
ON THIS PAGELearning objectivesName the type rule instead of hiding it in a long errorRead the definition, then the callrequires states a requirement, not a runtime branchFix the reason for rejection, not just the messageCompare bounded input using int and longIndependent labReviewKnowledge checkReferences
Course overview