Specializarea șablonului și expedierea etichetelor
Stăpâniți specializarea completă și parțială, aflați când să preferați supraîncărcarea și descoperiți trimiterea etichetelor și dacă constexpr ca alternative pentru ramificarea în timp de compilare.
De ce sa se specializeze?
Șablonul principal oferă o implementare generic care funcționează pentru majoritatea tipurilor. Dar uneori un anumit tip are nevoie de un tratament fundamental diferit. De exemplu,std::vectoreste o specializare care împachetează bool în biți pentru eficiența spațiului (deși este considerată în general o greșeală în biblioteca standard).
Specializarea șablonului vă permite să oferiți o definiție alternativă pentru anumite argumente șablon. Există două feluri:
1. Specializare completă (explicită) — remediați toți parametrii șablonului la anumite tipuri.
2. Specializare parțială — remediați unii parametri sau îi constrângeți (de exemplu, vă specializați pentru toate tipurile de pointer). Specializarea parțială este disponibilă numai pentru șabloanele de clasă, nu și pentru șabloanele de funcție.
Specializare completă
O specializare completă oferă o implementare complet personalizată pentru un set specific de argumente șablon. Sintaxa foloseștetemplate <>(o listă de parametri goală) urmată de clasa sau funcția cu argumente concrete.
#include <iostream>
#include <cstring>
// Primary template
template <typename T>
class Formatter {
public:
static std::string format(const T& value) {
return std::to_string(value);
}
};
// Full specialization for const char*
template <>
class Formatter<const char*> {
public:
static std::string format(const char* value) {
return std::string("\"" ) + value + "\"";
}
};
// Full specialization for bool
template <>
class Formatter<bool> {
public:
static std::string format(bool value) {
return value ? "true" : "false";
}
};
int main() {
std::cout << Formatter<int>::format(42) << '\n'; // 42
std::cout << Formatter<const char*>::format("hi") << '\n'; // "hi"
std::cout << Formatter<bool>::format(true) << '\n'; // true
}Specializare parțială
Specializarea parțială constrânge unii parametri șablon, în timp ce alții îi lasă generici. Acest lucru este extrem de puternic pentru potrivirea familiilor de tipuri — toți pointerii, toate matricele, toate perechile etc. Numai șabloanele clasă/struct/variabilă acceptă specializarea parțială.
#include <iostream>
#include <string>
#include <typeinfo>
// Primary template
template <typename T>
struct TypeInfo {
static std::string name() { return "general type"; }
};
// Partial specialization for all pointer types
template <typename T>
struct TypeInfo<T*> {
static std::string name() {
return "pointer to " + TypeInfo<T>::name();
}
};
// Partial specialization for all references
template <typename T>
struct TypeInfo<T&> {
static std::string name() {
return "reference to " + TypeInfo<T>::name();
}
};
// Partial specialization for arrays
template <typename T, std::size_t N>
struct TypeInfo<T[N]> {
static std::string name() {
return "array of " + std::to_string(N) + " " + TypeInfo<T>::name();
}
};
int main() {
std::cout << TypeInfo<int>::name() << '\n'; // general type
std::cout << TypeInfo<int*>::name() << '\n'; // pointer to general type
std::cout << TypeInfo<int**>::name() << '\n'; // pointer to pointer to general type
std::cout << TypeInfo<int[5]>::name() << '\n'; // array of 5 general type
}Șabloanele de funcție nu pot fi parțial specializate — doar complet specializate. Mai rău, specializările șablonului de funcție nu participă la rezolvarea supraîncărcării așa cum v-ați aștepta. Compilatorul selectează mai întâi cel mai bun șablon de bază prin rezoluția supraîncărcării și abia apoi verifică dacă acel șablon de bază are o specializare potrivită.
Acest lucru duce la un comportament surprinzător:
template <typename T> void f(T) { /* #1 - primary */ }
template <typename T> void f(T*) { /* #2 - overload for pointers */ }
template <> void f<int*>(int*) { /* #3 - specialization of #1 */ }Calling f((int*)nullptr) selects #2 (the better overload match), not #3 (which specializes #1, not #2). The fix: use regular function overloading instead of function template specialization. If you need compile-time type branching, use if constexpr sau concepte.
Model de expediere a etichetei
Trimiterea etichetelor folosește tipuri de structuri goale ("etichete") pentru a selecta supraîncărcările în timpul compilării. Biblioteca standard folosește acest lucru pe scară largă -std::advancetrimite pe etichete de categorie iteratoare. Aceasta a fost tehnica dominantă de ramificare în timp de compilare înainte deif constexpr.
#include <iostream>
#include <iterator>
#include <vector>
#include <list>
namespace detail {
// Tags are empty structs used purely for overload selection
template <typename Iter>
void advance_impl(Iter& it, int n, std::random_access_iterator_tag) {
it += n; // O(1) — random access
std::cout << "random access advance\n";
}
template <typename Iter>
void advance_impl(Iter& it, int n, std::bidirectional_iterator_tag) {
// O(n) — step one at a time, forward or backward
while (n > 0) { ++it; --n; }
while (n < 0) { --it; ++n; }
std::cout << "bidirectional advance\n";
}
template <typename Iter>
void advance_impl(Iter& it, int n, std::input_iterator_tag) {
while (n > 0) { ++it; --n; }
std::cout << "input iterator advance\n";
}
}
template <typename Iter>
void my_advance(Iter& it, int n) {
// iterator_traits extracts the category tag
detail::advance_impl(it, n,
typename std::iterator_traits<Iter>::iterator_category{});
}
int main() {
std::vector<int> v{1, 2, 3, 4, 5};
auto vit = v.begin();
my_advance(vit, 3); // random access advance
std::list<int> l{1, 2, 3, 4, 5};
auto lit = l.begin();
my_advance(lit, 3); // bidirectional advance
}if constexpr: Alternativa modernă
if constexpr de la C++17 oferă o modalitate mai curată de a ramifica în timpul compilării într-o singură funcție. Ramura aruncată nu este instanțiată, deci nu trebuie să fie compilată pentru tipul dat. Aceasta înlocuiește în mare măsură expedierea etichetelor și SFINAE pentru cazuri simple.
#include <iostream>
#include <type_traits>
#include <string>
#include <vector>
template <typename T>
std::string stringify(const T& value) {
if constexpr (std::is_arithmetic_v<T>) {
return std::to_string(value);
} else if constexpr (std::is_same_v<T, std::string>) {
return '"' + value + '"';
} else if constexpr (std::is_same_v<T, const char*>) {
return std::string("\"" ) + value + "\"";
} else {
return "[non-printable]";
}
}
int main() {
std::cout << stringify(42) << '\n'; // 42
std::cout << stringify(3.14) << '\n'; // 3.140000
std::cout << stringify(std::string("hello")) << '\n'; // "hello"
std::cout << stringify("world") << '\n'; // "world"
}Utilizați acest ghid de decizie pentru ramificarea în timp de compilare:
- if constexpr(C++17) — cel mai simplu, funcționează într-o singură funcție, nu sunt necesare modele speciale. Preferați acest lucru pentru o logică simplă bazată pe tipuri.
- Concepte/cerințe (C++20) — cel mai bun pentru limitarea supraîncărcărilor de funcții sau șabloane întregi. Se auto-documentează și oferă mesaje de eroare clare.
- Trimiterea etichetelor — încă util atunci când aveți o ierarhie de etichete existentă (cum ar fi categoriile de iteratoare). Bazele de cod pre-C++17 se bazează foarte mult pe acest lucru.
- SFINAE /enable_if — cea mai veche tehnică. Mai greu de citit, erori criptice. Evitați în codul nou dacă conceptele sauif constexprfuncționează.
- Specializare completă — utilizați pentru șabloanele de clasă atunci când aveți nevoie de o implementare complet diferită pentru un anumit tip.
- Niciodată utilizați specializarea șablonului de funcție - utilizați în schimb supraîncărcarea.
- Full specialization (
template <>) provides a custom implementation for one specific type - Partial specialization constrains some parameters — available for class templates only, not function templates
- Function template specialization is error-prone — prefer overloading or
if constexpr - Tag dispatch uses empty struct types to select overloads at compile time via
iterator_traits if constexpr(C++17) is the modern, cleaner alternative that discards untaken branches at compile time
Test — Verifică-ți cunoștințele
(20 XP)1. Why should you avoid specializing function templates?
2. Ce tipuri de șabloane acceptă specializarea parțială?
3. Ce face`if constexpr`diferit de un`if`obișnuit?