Giao diện
🚀 C++20 Concepts — Modern Template Constraints
Concepts là cách hiện đại để constrain templates — readable hơn SFINAE, error messages tốt hơn.
Vấn đề với Templates (Pre-C++20)
cpp
template<typename T>
T add(T a, T b) {
return a + b;
}
int main() {
add(1, 2); // ✅ OK
add(1.5, 2.5); // ✅ OK
add("hello", "world"); // ❌ Error: 50 dòng cryptic error message!
}Error message khó hiểu — không biết T cần có operator+ hay gì.
Concepts Giải quyết vấn đề
cpp
#include <concepts>
template<typename T>
concept Addable = requires(T a, T b) {
{ a + b } -> std::same_as<T>;
};
template<Addable T>
T add(T a, T b) {
return a + b;
}
int main() {
add(1, 2); // ✅ OK
add(1.5, 2.5); // ✅ OK
add("hello", "world"); // ❌ Error: "T does not satisfy Addable"
}Error message rõ ràng: "error: T does not satisfy concept 'Addable'"
Cú pháp Concepts
1. Concept Definition
cpp
template<typename T>
concept ConceptName = /* constraint expression */;2. Sử dụng Concepts
cpp
// Cách 1: Thay typename
template<Addable T>
T add(T a, T b);
// Cách 2: requires clause
template<typename T>
requires Addable<T>
T add(T a, T b);
// Cách 3: Trailing requires
template<typename T>
T add(T a, T b) requires Addable<T>;
// Cách 4: Abbreviated (auto + concept)
Addable auto add(Addable auto a, Addable auto b);Standard Library Concepts
<concepts> header cung cấp nhiều concepts sẵn:
Core Concepts
| Concept | Meaning |
|---|---|
std::same_as<T, U> | T và U là cùng type |
std::derived_from<D, B> | D kế thừa từ B |
std::convertible_to<From, To> | From có thể convert to To |
std::common_reference_with<T, U> | T và U có common reference |
Comparison Concepts
| Concept | Meaning |
|---|---|
std::equality_comparable<T> | Có == và != |
std::totally_ordered<T> | Có <, >, <=, >= |
Object Concepts
| Concept | Meaning |
|---|---|
std::movable<T> | Move constructible và assignable |
std::copyable<T> | Copy constructible và assignable |
std::regular<T> | Copyable + default constructible + equality |
Arithmetic Concepts
| Concept | Meaning |
|---|---|
std::integral<T> | int, short, long, etc. |
std::signed_integral<T> | Signed integers |
std::unsigned_integral<T> | Unsigned integers |
std::floating_point<T> | float, double, etc. |
requires Expression
cpp
template<typename T>
concept Printable = requires(T t) {
// Simple requirement: expression must be valid
std::cout << t;
// Type requirement: associated type must exist
typename T::value_type;
// Compound requirement with return type
{ t.size() } -> std::convertible_to<std::size_t>;
// Nested requirement
requires std::copyable<T>;
};requires Expression Examples
cpp
// Must have begin() and end()
template<typename T>
concept Iterable = requires(T t) {
t.begin();
t.end();
};
// Must be hashable
template<typename T>
concept Hashable = requires(T t) {
{ std::hash<T>{}(t) } -> std::convertible_to<std::size_t>;
};
// Must be incrementable
template<typename T>
concept Incrementable = requires(T t) {
{ ++t } -> std::same_as<T&>;
{ t++ } -> std::same_as<T>;
};Practical Examples
Only Integral Types
cpp
#include <concepts>
#include <iostream>
template<std::integral T>
T factorial(T n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
int main() {
std::cout << factorial(5) << std::endl; // ✅ 120
std::cout << factorial(10L) << std::endl; // ✅ 3628800
// factorial(5.0); // ❌ Error: 5.0 does not satisfy std::integral
return 0;
}Container Concept
cpp
template<typename C>
concept Container = requires(C c) {
typename C::value_type;
typename C::iterator;
{ c.begin() } -> std::same_as<typename C::iterator>;
{ c.end() } -> std::same_as<typename C::iterator>;
{ c.size() } -> std::convertible_to<std::size_t>;
{ c.empty() } -> std::convertible_to<bool>;
};
template<Container C>
void printSize(const C& container) {
std::cout << "Size: " << container.size() << std::endl;
}
int main() {
std::vector<int> v = {1, 2, 3};
std::list<double> l = {1.0, 2.0};
printSize(v); // ✅ Size: 3
printSize(l); // ✅ Size: 2
// printSize(42); // ❌ Error: int does not satisfy Container
return 0;
}Numeric Concept
cpp
template<typename T>
concept Numeric = std::integral<T> || std::floating_point<T>;
template<Numeric T>
T square(T x) {
return x * x;
}
int main() {
std::cout << square(5) << std::endl; // ✅ 25
std::cout << square(2.5) << std::endl; // ✅ 6.25
// square("hello"); // ❌ Error
return 0;
}Combining Concepts
cpp
// AND: both must be satisfied
template<typename T>
concept NumbericAndPrintable = Numeric<T> && Printable<T>;
// OR: at least one must be satisfied
template<typename T>
concept IntOrFloat = std::integral<T> || std::floating_point<T>;
// NOT: using requires
template<typename T>
concept NotPointer = !std::is_pointer_v<T>;
// Complex combination
template<typename T>
concept SmartNumber =
Numeric<T> &&
std::copyable<T> &&
requires(T t) {
{ t + t } -> std::same_as<T>;
{ t * t } -> std::same_as<T>;
};Concept Overloading
cpp
#include <concepts>
#include <iostream>
// Overload for integral
void process(std::integral auto x) {
std::cout << "Integer: " << x << std::endl;
}
// Overload for floating point
void process(std::floating_point auto x) {
std::cout << "Floating: " << x << std::endl;
}
// Fallback for others
template<typename T>
void process(T x) {
std::cout << "Generic: " << x << std::endl;
}
int main() {
process(42); // Integer: 42
process(3.14); // Floating: 3.14
process("hello"); // Generic: hello
return 0;
}Subsumption (More Specific Wins)
cpp
template<typename T>
concept Animal = requires(T t) { t.speak(); };
template<typename T>
concept Dog = Animal<T> && requires(T t) { t.bark(); };
// Dog subsumes Animal (Dog is more specific)
void handle(Animal auto a) {
std::cout << "Animal" << std::endl;
}
void handle(Dog auto d) {
std::cout << "Dog" << std::endl;
}
struct Cat { void speak() {} };
struct Puppy { void speak() {} void bark() {} };
int main() {
Cat c;
Puppy p;
handle(c); // Animal
handle(p); // Dog (more specific wins!)
return 0;
}Concepts vs SFINAE
| Feature | SFINAE | Concepts |
|---|---|---|
| Readability | ❌ Verbose | ✅ Clean |
| Error messages | ❌ Cryptic | ✅ Clear |
| Compile time | 🔸 Slower | ✅ Faster |
| Composability | 🔸 Hard | ✅ Easy |
| Debugging | ❌ Hard | ✅ Easier |
cpp
// SFINAE (old way)
template<typename T,
typename = std::enable_if_t<std::is_integral_v<T>>>
T oldWay(T x) { return x + 1; }
// Concepts (new way)
template<std::integral T>
T newWay(T x) { return x + 1; }📚 Tổng kết
| Syntax | Meaning |
|---|---|
template<typename T> concept C = ...; | Define concept |
template<C T> | Constrain T to C |
requires C<T> | requires clause |
requires(T t) { ... } | requires expression |
{ expr } -> C | Return type constraint |
Top 5 Standard Concepts
std::integral— int, long, short, etc.std::floating_point— float, doublestd::same_as<T, U>— Type equalitystd::convertible_to<From, To>— Implicit conversionstd::copyable— Copy semantics
🎉 Hoàn thành Part 5: Templates!
Bạn đã nắm vững:
- ✅ DRY với Templates
- ✅ Function Templates
- ✅ Class Templates (Box<T>)
- ✅ Template Specialization
- ✅ C++20 Concepts
Tiếp theo: Part 6 — Modern C++ (Smart Pointers, Move Semantics)