MySQL Java JavaScript PHP Python HTML-CSS C-sharp C++ Go

Expresia requires

Expresia requires este destinată pentru a specifica și detalia restricțiile pentru tipuri. Ea are următoarele forme:

  • requires { cerințe }
  • requires (parametri) { cerințe }

După cuvântul requires poate urma o listă opțională de parametri între paranteze rotunde, care este similară cu lista de parametri a unei funcții. După lista de parametri, între paranteze unghiulare, sunt specificate cerințele, care pot utiliza parametrii respectivi. Fiecare cerință se încheie cu un punct și virgulă. Cerințele pot fi simple sau compuse.

De asemenea, parametrii expresiei requires nu sunt legați de argumentele efective, iar expresiile din parantezele unghiulare nu sunt niciodată executate. Tot ce face compilatorul cu aceste expresii este să verifice dacă acestea formează un cod C++ valid.

Cerințe simple

O cerință simplă reprezintă o expresie C++ arbitrară. Dacă această expresie este validă pentru tipurile specificate, atunci tipul respectiv satisface cerința.

Pentru expresia requires pot fi definite mai multe cerințe, iar tipul trebuie să satisfacă toate aceste cerințe. De exemplu:

#include <iostream>
 
template <typename T>
concept operation = requires (T item)
{
    item + item; item - item; item * item;
};
 
class Counter{};
int main()
{
    std::cout << std::boolalpha << operation<int> << std::endl;         // true
    std::cout << std::boolalpha << operation<char> << std::endl;        // true
    std::cout << std::boolalpha << operation<std::string> << std::endl; // false
    std::cout << std::boolalpha << operation<Counter> << std::endl;     // false
}

Aici este definit conceptul operation, al cărui limitări sunt stabilite cu ajutorul expresiei requires:

requires (T item)
{
    item + item; item - item; item * item;
};

Expresia requires definește un parametru de tip T, pe care îl verificăm pentru a îndeplini cerințele. În acest caz, sunt definite trei cerințe: item + item, item - item, și item * item. Adică luăm un tip T și verificăm dacă expresiile respective sunt valide pentru un obiect de acest tip. Pentru ca aceste expresii să fie valide, tipul T trebuie să definească operațiile de adunare, scădere și înmulțire. De asemenea, tipul T trebuie să îndeplinească toate aceste cerințe.

În funcția main, verificăm diferite tipuri pentru a vedea dacă respectă conceptul operation. De exemplu:

operation<int>

Pentru tipul int, toate operațiile declarate sunt definite - adunarea, scăderea și înmulțirea. Prin urmare, această expresie va returna true. La fel și pentru tipul char cu expresia:

operation<char>

Dar pentru tipul std::string, doar operația de adunare (concatenarea șirurilor) este definită, așa că:

operation<std::string>  // false

De asemenea, pentru clasa noastră goală Counter, nu sunt definite nici o operație, astfel că:

operation<Counter>  // false

În expresiile din blocul requires pot fi folosite nu doar operații aritmetice sau alte operații. Pot fi orice expresii, cum ar fi apeluri de funcții, constructori, conversii de tipuri, acces la membri ai claselor, etc. Totuși, nu se pot defini variabile locale în interiorul parantezelor unghiulare. Toate variabilele folosite în expresii trebuie fie să fie variabile globale, fie să fie parametri ai listei de parametri. De exemplu, să definim un parametru suplimentar de tip int:

#include <iostream>
#include <vector>
 
template <typename T>
concept is_collection = requires (T collection, int n)
{
    collection[n];
};
  
int main()
{
    std::cout << std::boolalpha << is_collection<int> << std::endl;                 // false
    std::cout << std::boolalpha << is_collection<char[]> << std::endl;              // true
    std::cout << std::boolalpha << is_collection<std::string> << std::endl;         // true
    std::cout << std::boolalpha << is_collection<std::vector<int>> << std::endl;    // true
}

Aici este definit conceptul is_collection, care verifică dacă tipul T este o colecție și poate accesa elementele prin index.

Expresia requires acum primește doi parametri. Al doilea parametru este de tip int.

requires (T collection, int n)
{
    collection[n];
};

Aici este folosită o cerință: collection[n]. Adică tipul T trebuie să permită accesul la elemente folosind un index întreg.

Tipul int nu este o colecție, prin urmare va returna false pentru acest tip:

is_collection<int>  // false

Așadar, șirurile de caractere, array-urile și vectorii suportă accesul la elemente prin index, iar pentru aceștia expresia va returna true:

is_collection<char[]>                     // true
is_collection<std::string>                // true
is_collection<std::vector<int>>           // true

Acum să aplicăm expresia requires pentru a crea un concept pentru a restricționa un șablon:

#include <iostream>
#include <concepts>
 
template <typename T>
concept sum_types = requires (T x) { x + x; };  // T trebuie să suporte operația de adunare
 
template <sum_types T>
T sum(T a, T b) { return a + b; }
 
int main()
{
    std::cout << sum(10, 3) << std::endl;       // 13
    std::cout << sum(10.6, 3.2) << std::endl;   // 13.8
    std::cout << sum(std::string("Hello "), std::string("world")) << std::endl; // Hello world
}

În acest caz, restricția:

requires (T x) { x + x; };

indică faptul că tipul T trebuie să suporte operația de adunare. Acestea pot fi și numere, și șiruri de caractere std::string.

De asemenea, se poate folosi expresia requires direct după operatorul requires:

template <typename T> requires requires (T x) { x + x; } 
T sum(T a, T b) { return a + b; }

În acest caz, primul cuvânt requires reprezintă operatorul care impune restricția pentru șablon. Iar al doilea cuvânt requires reprezintă expresia care definește cerințele.

Cerințe compuse

O cerință compusă este similară cu o cerință simplă, dar poate, de asemenea, să interzică expresiei să genereze excepții și/sau să limiteze tipul evaluat. Cerințele compuse pot avea următoarele forme:

  • { expr }; // expr este orice expresie validă
  • { expr } noexcept; // expr este valid dacă nu generează excepții
  • { expr } -> restricție_t; // expr este valid dacă tipul respectă restricția_t
  • { expr } noexcept -> restricție_t; // expr este valid dacă nu generează excepții și tipul respectă restricția_t

Cerința { expr } noexcept va fi validă dacă toate funcțiile apelate în expresia expr sunt definite ca noexcept.

În cerința { expr } -> restricție_t;, după săgeată (->), trebuie să urmeze o restricție de tip. De exemplu:

#include <iostream>
#include <concepts>
 
template <typename Pointer>
concept is_pointer = requires (Pointer ptr, int n)
{
    {ptr[n]};                         // verificăm dacă se poate accesa un element prin index
    { ptr - n } -> std::same_as<Pointer>;  // verificăm dacă operația de scădere între pointer și int returnează un pointer
    { ptr + n } -> std::same_as<Pointer>;  // verificăm dacă operația de adunare între pointer și int returnează un pointer
};
 
int main()
{
    std::cout << std::boolalpha << is_pointer<int> << std::endl;    // false
    std::cout << std::boolalpha << is_pointer<int[]> << std::endl;  // false
    std::cout << std::boolalpha << is_pointer<int*> << std::endl;   // true
    std::cout << std::boolalpha << is_pointer<char*> << std::endl;  // true
}

Aici, conceptul is_pointer utilizează o cerință compusă formată din trei cerințe:

requires (Pointer ptr, int n)
{
    {ptr[n]};                             // putem accesa un element prin index
    { ptr - n } -> std::same_as<Pointer>;  // tipul rezultat din ptr - n trebuie să fie același cu Pointer
    { ptr + n } -> std::same_as<Pointer>;  // tipul rezultat din ptr + n trebuie să fie același cu Pointer
};

Cerința ptr[n] verifică dacă putem accesa valorile aplicând operația de indexare. Urmează cerințele cu restricții de tip:

{ ptr - n } -> std::same_as<Pointer>;

În acest caz, pentru tipul Pointer trebuie să se suporte operația de scădere, iar rezultatul acestei operații trebuie să fie tot de tip Pointer. Restricția de tip este aplicată folosind conceptul încorporat std::same_asdin fișierul de antetconcepts`.

Conceptului cu restricții similare îi vor corespunde, de exemplu, pointerele către valori de orice tip:

is_pointer<int*>  // true
is_pointer<char*> // true

De exemplu, să definim o cerință compusă pentru a restricționa un șablon:

#include <iostream>
#include <concepts>
 
template <typename T>
concept sum_types = requires (T x) { {x + x} -> std::convertible_to<T>; };
 
template <sum_types T>
T sum(T a, T b) { return a + b; }
 
int main()
{
    std::cout << sum(10, 3) << std::endl;       // 13
    std::cout << sum(10.6, 3.2) << std::endl;   // 13.8
    std::cout << sum(std::string("Hello "), std::string("world")) << std::endl; // Hello world
}

Aici cerința prevede că tipul T trebuie să susțină operația de adunare, iar rezultatul acestei operații trebuie să fie un tip care poate fi convertit în tipul T (sau chiar același tip T).