ABONAMENTE VIDEO REDACȚIA
RO
EN
NOU
Numărul 148
Numărul 147 Numărul 146 Numărul 145 Numărul 144 Numărul 143 Numărul 142 Numărul 141 Numărul 140 Numărul 139 Numărul 138 Numărul 137 Numărul 136 Numărul 135 Numărul 134 Numărul 133 Numărul 132 Numărul 131 Numărul 130 Numărul 129 Numărul 128 Numărul 127 Numărul 126 Numărul 125 Numărul 124 Numărul 123 Numărul 122 Numărul 121 Numărul 120 Numărul 119 Numărul 118 Numărul 117 Numărul 116 Numărul 115 Numărul 114 Numărul 113 Numărul 112 Numărul 111 Numărul 110 Numărul 109 Numărul 108 Numărul 107 Numărul 106 Numărul 105 Numărul 104 Numărul 103 Numărul 102 Numărul 101 Numărul 100 Numărul 99 Numărul 98 Numărul 97 Numărul 96 Numărul 95 Numărul 94 Numărul 93 Numărul 92 Numărul 91 Numărul 90 Numărul 89 Numărul 88 Numărul 87 Numărul 86 Numărul 85 Numărul 84 Numărul 83 Numărul 82 Numărul 81 Numărul 80 Numărul 79 Numărul 78 Numărul 77 Numărul 76 Numărul 75 Numărul 74 Numărul 73 Numărul 72 Numărul 71 Numărul 70 Numărul 69 Numărul 68 Numărul 67 Numărul 66 Numărul 65 Numărul 64 Numărul 63 Numărul 62 Numărul 61 Numărul 60 Numărul 59 Numărul 58 Numărul 57 Numărul 56 Numărul 55 Numărul 54 Numărul 53 Numărul 52 Numărul 51 Numărul 50 Numărul 49 Numărul 48 Numărul 47 Numărul 46 Numărul 45 Numărul 44 Numărul 43 Numărul 42 Numărul 41 Numărul 40 Numărul 39 Numărul 38 Numărul 37 Numărul 36 Numărul 35 Numărul 34 Numărul 33 Numărul 32 Numărul 31 Numărul 30 Numărul 29 Numărul 28 Numărul 27 Numărul 26 Numărul 25 Numărul 24 Numărul 23 Numărul 22 Numărul 21 Numărul 20 Numărul 19 Numărul 18 Numărul 17 Numărul 16 Numărul 15 Numărul 14 Numărul 13 Numărul 12 Numărul 11 Numărul 10 Numărul 9 Numărul 8 Numărul 7 Numărul 6 Numărul 5 Numărul 4 Numărul 3 Numărul 2 Numărul 1
×
▼ LISTĂ EDIȚII ▼
Numărul 80
Abonament PDF

Concepte: Noua modă pentru template-urile din C++

David Harabagiu
QT Developer @ Telenav



PROGRAMARE

Limbajul C++ a fost descris de către Scott Meyers ca fiind o federație de limbaje înrudite. Fiecare dintre aceste sublimbaje vine cu propriul set de reguli și mod de a scrie lucrurile, iar pentru a le aprofunda, fiecare trebuie privit din alt unghi și studiat cu o altă mentalitate. El a identificat patru astfel de limbaje: C, C++ orientat pe obiecte, C++ cu template-uri și STL. C++ poate fi un limbaj care intimidează prin complexitatea acestuia: atunci când scriem cod, trebuie să ținem cont de toate sublimbajele și paradigmele aflate în legătură cu acest limbaj. De asemenea, C++ este în continuă schimbare, pentru că o nouă iterație își face apariția o dată la trei ani, când apar noi concepte pe care este necesar să le avem în vedere. În acest fel, monstrul C++ devine și mai mare și fioros. De la introducerea lor, template-urile au avut și ele parte de acest tratament: template-uri variadice în C++11, lambda-uri templetizate în C++14, etc. Standardul C++20 nu va fi excepție și va aduce și el noi surprize, printre care "Conceptele".

Template-uri

Template-urile sunt mecanismul prin care putem scrie cod generic în C++. O diferență majoră față de genericele din C# este că template-urile sunt deduse la compile time și astfel scăpăm de overheadul cauzat de deducerea tipurilor în timpul execuției. Se aseamănă mai mult cu genericele din Java și mecanismul de type erasure, însă nu sunt atât de rigide. De asemenea, în C++ putem folosi variabile pentru parametrii unui template. Template-urile sunt astfel foarte puternice, metaprogramarea cu template-uri în C++ fiind turing complete. Putem scrie întregi algoritmi care să ruleze la compile time, ca de exemplu, calcularea numerelor Fibonacci:

template
constexpr int fibonacci = fibonacci 
  + fibonacci;

template<>
constexpr int fibonacci<0> = 0;

template<>
constexpr int fibonacci<1> = 1;

Template type checking în C++20

Parametrii de tip ai template-urilor acceptă orice. Din cauza acestei libertăți pot interveni probleme atunci când dăm un tip care nu este disponibil unei funcții sau clase templetizate. De exemplu, ce se întâmplă când apelăm std::sort pe un container de elemente al căror tip nu definește operatori de comparație? Compilerul va ajunge la un moment dat la o expresie de genul if (element1 < element2) și ne va da multe erori:

error: C2672: 'operator __surrogate_func': 
  no matching overloaded function found

error: C2893: Failed to specialize function template 'unknown-type std::less::operator ()
  (_Ty1 &&,_Ty2 &&) const'

... și multe altele ...

Pentru că nu am specificat un functor pentru comparație, cel implicit, std::less, este folosit. std::less compară cele două argumente folosind operatorul "<", care nu este definit pentru tipul nostru. Aceste mesaje de eroare pot intra în detalii foarte tehnice care nu sunt foarte evidente. Mai mult, cu toate că acestea au semnificație pentru compilator, unui programator îi pot da bătăi de cap.

Tocmai de aceea, în C++20 vor fi introduse conceptele. Acestea vor permite programatorului să definească categorii semantice ce au o semnificație pentru el, din care pot face parte parametrii template-urilor. Altfel spus, cu ajutorul conceptelor ne putem defini constrângeri pentru template-uri și să le grupăm sub un nume care are pentru programator o semnificație anume. Putem privi conceptele ca fiind echivalentul interfețelor, dar pentru argumentele template-urilor. În contextul exemplului anterior, putem identifica o astfel de categorie ce conține tipurile comparabile cu operatorul "<". Această categorie ar putea fi scrisă drept concept în C++ astfel:

template
concept Comparable = requires(T t1, T t2) {
    { t1 < t2 } -> bool;
};

În interiorul clauzei "requires", este specificat că, având două obiecte t1 și t2 de tip T, expresia "t1 < t2" trebuie să compileze și returnează un tip ce poate fi convertit în mod implicit la "bool". O funcție a cărei parametru de template trebuie să adere la conceptul "Comparable" poate fi declarată într-unul din următoarele moduri:

template
compare(T t1, T t2) {
    ...
}

template requires Comparable
compare(T t1, T t2) {
    ...
}

În cazul în care tipurile argumentelor nu vor respecta conceptul "Comparator" (vom folosi tipul "Foo"), erorile ne vor ajuta să suferim mult mai puțin:

error: cannot call compare with 'Foo' and 'Foo' 
note:  concept 'Comparable' was not satisfied

Alternativ, pentru std::sort, putem avea un concept "Comparator" pentru functorul de comparație, care trebuie să aibă definit operatorul "( )", ce are doi parametri de tipul elementelor din container și returnează un boolean.

template
concept Comparator = requires(F f, It i1, It i2) {
    { f(*i1, *i2) } -> bool;
};

Acum, declarația funcției std::sort, constrânsă cu conceptul "Comparator", devine:

template
requires Comparator
sort(Iterator begin, Iterator end, Compare comp);

Concepte în specializarea template-urilor

Conceptele mai pot fi utilizate și atunci când specializăm funcțiile sau clasele templetizate. Compilatorul va putea alege varianta template-ului ce satisface proprietățile tipului definite de către concept. Putem astfel să tratăm diferit anumite categorii de tipuri, atunci când un tip îndeplinește anumite condiții de care ne putem folosi pentru a implementa pentru acesta, o variantă specializată și mai rapidă a unui algoritm sau dorim pur și simplu alt comportament. De exemplu, un counting sort e una dintre cele mai bune metode de a sorta numere întregi mici, pentru care putem scrie conceptul:

template
SmallInteger = requires {
    sizeof(T) == 1;
    std::is_integral::value;
};

template sort(T* begin, T* end) {
    // Counting sort
}

Sau, dacă valorile sortate sunt foarte mari, e mai eficient să construim o listă separată de pointeri, să sortăm pointerii, apoi să construim din nou lista sortată de elemente:

template
LargeElement = requires {
    sizeof(T) > 8;
};

Putem obține un comportament asemănător conceptelor și în versiunile existente ale limbajului, cu ajutorul "std::enable_if", însă sintaxa este mult mai urâtă, iar acele două condiții sunt mai puțin evidente decât "SmallInteger".

template

typename std::enable_if::value>::type

sort(T lhs, T rhs)
{
    // Counting sort
}

Concluzii

Sunt multe de spus despre concepte. Cei pasionați pot intra pe cppreference pentru mai multe detalii tehnice. Use case-urile nu sunt atât de multe pentru programarea în C++ de zi cu zi și cel mai mult i-ar interesa pe cei care scriu biblioteci. Totuși, nu va trebui să folosim acest feature ca acesta să ne ușureze munca, căci vom avea o versiune de Standard Template Library ce va folosi concepte. Atunci când un parametru de template al unei funcții sau clase din STD nu poate fi dedus, în locul unui mesaj de eroare obscur, compilatorul ne va putea spune pur și simplu că un anumit concept nu este compatibil cu argumentele date.

Conferință TSM

NUMĂRUL 147 - Automotive

Sponsori

  • Accenture
  • BT Code Crafters
  • Accesa
  • Bosch
  • Betfair
  • MHP
  • BoatyardX
  • .msg systems
  • P3 group
  • Ing Hubs
  • Cognizant Softvision
  • Colors in projects