concept în C++

Dă un nume unui set de cerințe pentru tipuri, folosit ca să restrângi ce tipuri acceptă un șablon.

Sintaxă
template <typename T>
concept Nume = condiție;

template <Nume T> void f(T x);
void g(Nume auto x);

Ce face concept

Un concept este o condiție despre un tip, evaluată la compilare: „T este un număr întreg”, „T se poate aduna”, „T are metoda size()”. Îl folosești în locul lui typename: template <std::integral T> acceptă doar tipuri întregi.

Avantajul principal: dacă apelezi șablonul cu un tip nepotrivit, eroarea spune clar ce cerință nu este îndeplinită, în loc de zeci de rânduri despre interiorul șablonului. Codul devine și documentație: cerințele se văd în semnătură.

Biblioteca standard are multe concepte gata făcute în <concepts>: std::integral, std::floating_point, std::same_as, std::convertible_to, std::totally_ordered. Conceptele proprii se definesc de obicei cu o expresie requires.

Exemple

Exemplul 1: Concepte standard în semnătură

Forma scurtă std::integral auto este echivalentă cu un șablon cu parametru constrâns. jumatate("text") ar da o eroare clară: const char* nu satisface std::floating_point.

#include <concepts>
#include <print>

template <std::integral T>
bool este_par(T n) {
    return n % 2 == 0;
}

double jumatate(std::floating_point auto x) {
    return x / 2;
}

int main() {
    std::println("10 este par: {}", este_par(10));
    std::println("7L este par: {}", este_par(7L));
    std::println("Jumătate din 5.5: {}", jumatate(5.5));
}

Rezultat

10 este par: true
7L este par: false
Jumătate din 5.5: 2.75

Exemplul 2: Concept propriu

AreArie cere ca tipul să aibă o metodă aria() al cărei rezultat se poate transforma în double. Ambele structuri o au, deci sunt acceptate.

#include <concepts>
#include <print>

template <typename T>
concept AreArie = requires(const T& forma) {
    { forma.aria() } -> std::convertible_to<double>;
};

struct Patrat { double l; double aria() const { return l * l; } };
struct Triunghi { double b, h; double aria() const { return b * h / 2; } };

template <AreArie T>
void raport(const T& forma) {
    std::println("Aria: {:.2f}", forma.aria());
}

int main() {
    raport(Patrat{3});
    raport(Triunghi{4, 5});
    std::println("int are arie: {}", AreArie<int>);
}

Rezultat

Aria: 9.00
Aria: 10.00
int are arie: false

Greșeli frecvente

  • Definești conceptul prea larg (de exemplu doar „are +”) și apoi folosești în șablon și alte operații. Conceptul nu verifică restul, deci erorile vechi, lungi, revin.
  • Încerci să definești un concept în interiorul unei funcții sau al unei clase. Conceptele se definesc doar la nivel de spațiu de nume.

Comenzi înrudite

  • requires: Scrie cerințe pentru parametrii unui șablon: ca clauză care restrânge șablonul sau ca expresie care verifică dacă un cod este valid.
  • template: Scrie o funcție sau o clasă o singură dată, pentru orice tip; compilatorul generează varianta potrivită pentru fiecare tip folosit.
  • if constexpr: Un if evaluat la compilare: ramura falsă este eliminată complet, deci poate conține cod care nu s-ar compila pentru tipul curent.
  • static_assert: Verifică o condiție la compilare; dacă este falsă, compilarea se oprește cu mesajul dat.
  • std::integral: Concepte C++20 care restrâng un șablon la tipuri întregi (integral) sau reale (floating_point).