Skip to content

🚀 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

ConceptMeaning
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

ConceptMeaning
std::equality_comparable<T>Có == và !=
std::totally_ordered<T>Có <, >, <=, >=

Object Concepts

ConceptMeaning
std::movable<T>Move constructible và assignable
std::copyable<T>Copy constructible và assignable
std::regular<T>Copyable + default constructible + equality

Arithmetic Concepts

ConceptMeaning
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

FeatureSFINAEConcepts
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

SyntaxMeaning
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 } -> CReturn type constraint

Top 5 Standard Concepts

  1. std::integral — int, long, short, etc.
  2. std::floating_point — float, double
  3. std::same_as<T, U> — Type equality
  4. std::convertible_to<From, To> — Implicit conversion
  5. std::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)