C++20 concepts and ranges
Use concepts to constrain templates and ranges to express algorithms over sequences more clearly.
Learning objectives
- Explain why template constraints improve diagnostics and interfaces.
- Use a standard concept such as
std::integral. - Understand the C++20 ranges library and range-based algorithms.
- Use a simple view pipeline.
Prerequisites: understand templates, iterators, std::vector, and lambda expressions. Review Templates, Iterator categories, Vectors, and Lambdas first. Examples require a compiler and standard library with C++20 ranges support.
Why concepts?
Templates allow a function to work with many types. Without constraints, an invalid type may produce a long error deep inside a template. A concept names a set of requirements a type must satisfy. It makes the intended interface clearer and can reject unsuitable types earlier.
#include <concepts>
#include <iostream>
template<std::integral T>
T add_one(T value) {
return value + 1;
}
int main() {
std::cout << add_one(41) << '\n';
}std::integral is a standard concept in <concepts>. It accepts integral types such as int and rejects types such as std::string. Concepts constrain what a template accepts; they do not automatically make every operation safe from overflow.
requires expressions
template<typename T>
requires requires(T item) { item.size(); }
auto length_of(const T& value) {
return value.size();
}The outer requires clause constrains the template. The inner requires-expression checks whether the expression item.size() is valid for T.
Ranges algorithms
#include <algorithm>
#include <ranges>
#include <vector>
std::vector<int> values{8, 1, 6, 3};
std::ranges::sort(values);
auto found = std::ranges::find(values, 6);Ranges algorithms can accept a range directly rather than requiring a begin/end pair. They still obey the requirements of the algorithm: sorting needs a suitable sortable range.
Views and pipelines
#include <iostream>
#include <ranges>
auto result = values
| std::views::filter([](int x) { return x % 2 == 0; })
| std::views::transform([](int x) { return x * x; });
for (int x : result) std::cout << x << ' ';A view is generally a lightweight, non-owning description of elements or a transformation. The source range must remain alive while the view is used. Many views are lazy: the transformation happens as elements are traversed rather than creating a new vector immediately.
Compile with C++20
g++ -std=c++20 -Wall -Wextra -pedantic main.cpp -o app
Common mistakes
- Forgetting
-std=c++20or equivalent compiler configuration. - Assuming a view owns the source data.
- Using a range after its source container has been destroyed.
- Thinking concepts validate runtime values; they constrain types and expressions at compile time.
Practice
- Constrain a template to floating-point types with
std::floating_point. - Sort a vector with
std::ranges::sort. - Build a view that selects positive values and doubles them.
Show solutions
- Use
template<std::floating_point T>. - Call
std::ranges::sort(values). - Pipe through
views::filterthenviews::transform.
Summary
Concepts describe template requirements; ranges provide range-oriented algorithms and lazy views. Both improve the expressiveness of modern generic C++.