C++20: Concepts, Ranges, Coroutines
C++20 — concepts, ranges, coroutines
1. Concepts — Constrained Templates
Concepts state requirements on template parameters. Errors become readable, intent becomes clear.
#include <iostream>
#include <concepts>
template<std::integral T>
T square(T x) { return x * x; }
template<typename T>
concept Addable = requires(T a, T b) { a + b; };
template<Addable T>
T add(T a, T b) { return a + b; }
int main() {
std::cout << square(5) << "\n";
// square(5.0); // ERROR: not integral, clean diagnostic!
std::cout << add(3, 4) << "\n";
}
2. Ranges & Pipe Syntax
#include <iostream>
#include <vector>
#include <ranges>
int main() {
std::vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8};
auto view = v
| std::views::filter([](int x){ return x % 2 == 0; })
| std::views::transform([](int x){ return x * x; });
for (int x : view) std::cout << x << " ";
// 4 16 36 64
}
Views are lazy and composable — like Unix pipes for data.
3. Spaceship <=>
Default all 6 comparison operators in one line:
struct Point {
int x, y;
auto operator<=>(const Point&) const = default;
};
// Now Point supports <, <=, >, >=, ==, != !
4. Coroutines (Brief)
Functions that can suspend and resume. Core C++20 syntax:
co_await, co_yield, co_return.
Powerful but currently library-heavy — most users wait for community libraries to mature.
5. Modules (Brief)
Replacement for headers — faster compiles, better encapsulation. Adoption is gradual; major compilers and build systems are still maturing support. Worth watching for new projects starting in 2026.
export module math;
export int square(int x) { return x * x; }
6. Practice Problems
- Constrain T to be floating-point.
✨ Show Answer
template<std::floating_point T> T f(T x) { ... } - Define a Comparable concept.
✨ Show Answer
template<typename T> concept Comparable = requires(T a, T b) { { a < b } -> std::convertible_to<bool>; }; - Pipe: take first 3 squares of even numbers.
✨ Show Answer
auto r = v | std::views::filter(even) | std::views::transform([](int x){ return x*x; }) | std::views::take(3); - Are ranges eager or lazy?
✨ Show Answer
Lazy. Views compute on demand. No intermediate vectors are built.
- Default all comparisons for a struct.
✨ Show Answer
auto operator<=>(const X&) const = default; - Why are concepts more readable than SFINAE?
✨ Show Answer
Constraints stated up front, errors point at the violated requirement, intent is documented.
- Convert range view to vector.
✨ Show Answer
auto v = view | std::ranges::to<std::vector>(); // C++23 - Iota a range of 1..10.
✨ Show Answer
for (int i : std::views::iota(1, 11)) ... - Use a concept inline with auto.
✨ Show Answer
void f(std::integral auto x) { ... } - What returns the spaceship operator?
✨ Show Answer
An ordering type —
std::strong_orderingfor ints,std::partial_orderingfor floats, etc. - Why might modules speed up compilation?
✨ Show Answer
Modules are compiled once and cached. Headers are textually re-parsed for every translation unit that includes them.
- A coroutine that yields squares.
✨ Show Answer
Conceptual:
generator<int> squares() { for (int i = 1;; ++i) co_yield i*i; }— needs a generator type from a library. - requires expression for a type with size().
✨ Show Answer
template<typename T> concept Sized = requires(T t) { { t.size() } -> std::convertible_to<size_t>; }; - Why do error messages get cleaner with concepts?
✨ Show Answer
Compiler checks the concept first and reports the specific requirement that failed, not buried instantiation errors deep in template internals.
Summary
C++20 brings concepts (constrained templates with clean errors), ranges (lazy, pipe-composable views), coroutines, modules, and the spaceship operator. These are the biggest leap since C++11 — start using concepts and ranges in new code today.