Skip to content

Type Traits e Concetti (Concepts)

Il grande problema dei template è che, un po’ come nei linguaggi “duck-typed” come Python (“se cammina come una papera e fa quack come una papera, è una papera”), il compilatore si accorge che qualcosa non va solo quando entra dentro la funzione e prova a instanziarla.

Se provassimo a passare un array di puntatori a double alla nostra funzione mean (che cerca di dividerli), la compilazione esploderebbe vomitando schermi interi di errori illeggibili e orripilanti, perché i puntatori non supportano la divisione (/).

Il C++ Moderno offre strumenti per bloccare questi errori sul nascere imponendo vincoli chiari.

I Type Traits (Tratti di Tipo), inclusi nell’header <type_traits>, sono librerie preesistenti capaci di esaminare la natura di un tipo durante la compilazione.

Ad esempio std::is_integral<T>::value restituisce true o false a compile-time a seconda che T sia un numero intero. Esistono trait per tutto: is_floating_point, is_pointer, is_copy_constructible, ecc.

Poiché i Type Traits si calcolano durante la compilazione, possiamo bloccare a priori un template errato usando lo static_assert. Lo static_assert fallisce a compile-time, permettendoti di sputare in faccia all’utente incauto un messaggio d’errore leggibile, invece di lasciargli interpretare il crash del compilatore.

static_assert è una cura provvisoria. Il vero futuro della metaprogrammazione (finalmente standardizzato in C++20) si chiama Concepts (Concetti). Un Concept è una predicate expression che vive prima ancora di entrare nella funzione. Costringe formalmente un parametro T a possedere certe peculiarità (requires).

Con i Concetti, puoi definire un requisito formale e vincolarlo direttamente nella definizione:

// Sintassi fittizia per mostrare la potenza dei Concepts (C++20)
template <typename T>
concept Averageable = std::is_default_constructible_v<T> && requires(T a, T b) {
{ a += b } -> std::same_as<T&>;
{ a / size_t{1} } -> std::same_as<T>;
};
// Sostituiamo il banale 'typename' con il rigoroso Concept 'Averageable'
template <Averageable T>
T mean(const T* values, size_t length) {
// ...
}

Usando i Concetti, l’errore del compilatore diverrà magistrale: “Errore: Impossibile chiamare mean() con ‘const char’. Il vincolo ‘Averageable’ non è stato soddisfatto.”* I Concetti sono il ponte concettuale che collega la rigidità e l’ordine delle interfacce (Runtime) con l’incredibile potenza dei Template (Compile-time).