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.
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.

Use a small named type rule
Both calls use one consistent type per call. boolalpha prints readable true-or-false results.
Predict the output, edit the source, then select Run code.
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.
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.
Predict the output, edit the source, then select Run code.
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.
Read the same rule with requires
The boolean type test is a compile-time check. No invalid boolean call is executed.
Predict the output, edit the source, then select Run code.
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.
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.exeIndependent 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?
- All three parameters of one within call use the same T.
- Check the input domain and lower <= upper before the calls.
- A type assertion does not replace tests of actual endpoint values.
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.
Predict the output, edit the source, then select Run code.
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
Reference lab solution
Compare the reference with your own reasoning. Running this solution does not grade or complete the lesson.
Predict the output, edit the source, then select Run code.
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.